1.前言html
原本想作一個比較完善的監控平臺,只須要作少量改動就能夠直接拿來用,可是在作的過程當中發現要實現這個目標所需的工做量太大,而當前的工做中對其需求又不是特別明顯。因此就退而求其次,作了一個相似教程系統的東西。在這個系統中,你應該能夠找到作一個監控系統所須要的大部分技術點,而它的真正意義就在於其打通了整個數據流轉的環節。python
先上一個效果圖:mysql
由於只是作了一個簡單的驗證,因此只有內存曲線有變更,CPU使用狀況沒變化。X軸的座標有時間重複,是因爲數據裏面有相同的IP地址形成的。linux
磁盤使用狀況、硬盤/網絡IO的實現過程和內存、CPU相似,因此就沒有具體實現。須要特別說明的是: 數據採用的拉的方式,因此目標機只能執行shell命令,磁盤使用狀況的處理,尤爲是IO狀況的處理,比較麻煩。可是其好處是不須要在目標機器上安部署任何東西,只須要一個有權限執行shell命令的帳號便可。若是不介意部署麻煩問題,能夠在每一個目標機上安裝psutil包,能夠很方便的獲取系統信息,可是我看了看大部分系統自帶的python都是2.7版本的,而psutil包須要3.0版本以上的。git
2.遇到的問題github
把開發過程當中遇到的問題放到前面說,是爲了不你們遇到相同的問題,從而走了彎路。ajax
2.1 flask-sqlalchemysql
這個是我最想吐槽的地方:若是一個ORM框架的使用成本和排查錯誤的成本遠遠超過了直接使用sql語句的成本,那麼你還有啥存在的意義?shell
(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)
import paramiko #引用封裝SSH鏈接方式的工具包 #返回一個鏈接 #host:ip地址 username:用戶名 pwd 密碼 def get_connection(host,username,pwd): try: client = paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) client.connect(host,22,username,pwd) return client except Exception as e: return None
(2)使用configparser工具包讀取鏈接mysql數據庫的配置,並返回一個mysql鏈接(完整代碼:\woodpecker\wpgd\DBconn.py)
def __get_dbconn(self): try: _config = configparser.ConfigParser() _config.read(self._configPath) host = _config.get("dbconfig","host") #主機IP地址 dbusername = _config.get("dbconfig","dbusername") #數據庫用戶名 dbuserpwd = _config.get("dbconfig","dbuserpwd") #數據庫用戶密碼 dbname = _config.get("dbconfig","dbname") #數據庫名稱 db = pymysql.connect(host,dbusername,dbuserpwd,dbname) return db except Exception as e: return None
(3)使用pymysql讀取數據(完整代碼:\woodpecker\wpgd\DBconn.py)
def get_serverList(self,sqltext): db = self.__get_dbconn() if db is None: return 0 #0表示鏈接數據庫失敗 else : try: cursor = db.cursor() cursor.execute(sqltext) #執行sql語句 results = cursor.fetchall() #讀取所有結果 return results except Exception as e: return -1 # -1 表示讀取數據失敗,有可能SQL語句不對 finally : db.close()
4.2讀取數據部分(代碼都在app.py 和index.html中)
(1)flask-sqlalchemy鏈接mysql
from flask_sqlalchemy import SQLAlchemy #python3 的寫法 app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://woodpecker:woodpecker@localhost/woodpecker' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False #是否 app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True db = SQLAlchemy(app)
詳細配置參數見:http://www.pythondoc.com/flask-sqlalchemy/config.html
(2)models定義部分
這裏只有2點須要注意:
第一:若是定義的類名和表名不一致,須要使用__tablename__參數說明(__是雙下劃線),例如: __tablename__ = 't_servers'
第二:表必需要有主鍵
(3)無參數讀取數據庫數據
html部分:
<ul id="serverlist" class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu1"> {% for group in groupList %} <li><a role="menuitem" href="#" >{{ group.groupname }}</a></li> <li class="divider"></li> {% else %} <li><a role="menuitem" href="#">讀取服務器分組失敗</a></li> {% endfor %} </ul>
{{ group.groupname }} 這是flask表示變量的方式,而且上述代碼中的class都是在bootstrap中定義的。
python部分:
def index(): groupList = Group.query.all() return render_template('index.html', groupList=groupList)
Group.query.all() 這是flask-sqlalchemy查詢數據的方式
(4)頁面傳參數到後臺讀取數據
html部分(ajax):
var data = {'ip':ip}; //ip是傳到後臺的參數
$.ajax({
type:'post',
async:false,
url:"/get_sys_info", //接收參數的後臺路由函數
data:data,
success:function (result) {
drawchart(result,ip) //此函數是接收到返回的結果後作前臺處理的
;}
});
python部分:
@app.route('/get_sys_info', methods=['POST','GET']) def getSysInfo(): ip = request.form.get("ip") .......
(5)動態添加li元素
有兩種方式:
第一種在頁面裝載過程當中動態添加li元素及點擊事件:
//給服務器列表的下拉框每一個li元素添加點擊事件 window.onload = function () { var obj_lis = document.getElementById("serverlist").getElementsByTagName("li"); for(var i=0;i<obj_lis.length;i++) { obj_lis[i].onclick = function() { getiplist(this.innerText); } } } //根據選擇的分組設置btn的現實,並讀取當前分組下的ip列表 function getiplist(groupname) { var btn=document.getElementById("dropdownMenu1"); //直接給btn.innerText = groupname,表示下來的倒三角不顯示 btn.innerHTML=groupname+"<span class=\"caret\"></span>"; getipList(groupname) }
第二種當頁面裝載完成後點擊頁面元素局部刷新添加li元素及點擊事件
//添加ip地址到ul中 function addIPToUL(data){ delIPFromUL(); var ip_str = String(data); var ips = ip_str.split(","); //alert(ips[0]); for(var i=0;i<ips.length;i++) { var li = document.createElement("li"); li.setAttribute("class","list-group-item"); li.innerText=ips[i]; li.onclick=getSysInfo; document.getElementById("iplist").appendChild(li); } }
(6)動態刪除li元素
//選擇新分組以後須要清空當前的ip地址
function delIPFromUL() {
var obj_ul = document.getElementById("iplist");
var obj_lis = document.getElementById("iplist").getElementsByTagName("li");
var cnt = obj_lis.length;
//alert(cnt);
if(cnt>0) {
for (var i=cnt-1;i>=0;i--){
var m_li=obj_lis[i];
obj_ul.removeChild(m_li);
}
}
}
(7)定時刷新函數
setInterval(function () {
//alert("開始執行");
getSysInfo();
}, 60000);
這裏須要說一下,定時函數中執行的函數最好緊跟着定時函數,不然容易識別不了。
(8)時間格式轉換
js好像沒有自帶的時間轉換格式函數,因此引入了moment.js來處理時間格式。
(9)highcharts
html部分:
<div id="chart_cpu" style="height: 50%;width: 50%;background: white;float: left"></div>
js部分:
//CPU顯示 var options_cpu ={ chart:{ type:'line' }, title:{ text:'CPU使用狀況' }, subtitle:{ text:ip }, xAxis:{ type:"datetime", dateTimeLabelFormat:{ day:'%H:%M' }, labels:{ overflow:'justify' }, categories:data["time"] }, yAxis:{ title:{ text:'百分比(%)' } }, legend:{ layout: "vertical", align:'left', verticalAlign: "middle" }, credits:{ enabled:false }, series:[{ name:'sy', data:data["sy"] }, { name:'us', data:data["us"] }, { name:'total', data:data["totalcpu"] }] }; $('#chart_cpu').highcharts(options_cpu);
python部分:
@app.route('/get_sys_info', methods=['POST','GET']) def getSysInfo(): ip = request.form.get("ip") #倒序排列去除最近10條數據 sysinfos = SysInfo.query.filter_by(serverip=ip.strip()).order_by(SysInfo.id.desc()).limit(10).all() #從新處理成正序 sysinfolist = [] for i in range(len(sysinfos)-1, 0, -1): sysinfolist.append(sysinfos[i]) print(sysinfolist[0].id) cpudic = getCPUinfoList(sysinfolist) memdic = getMeminfoList(sysinfolist) sysinfodic = dict(cpudic, **memdic) sysinfodic = jsonify(sysinfodic) return sysinfodic
(10) div排列
<div id="mainchart" style="height: 80%;width:87%;background: #8c8c8c;position: absolute;margin-left: 13%"> <div id="chart_cpu" style="height: 50%;width: 50%;background: white;float: left"></div> <div id="chart_mem" style="height: 50%;width: 50%;background:beige;float: right" ></div> <div id="chart_disk" style="height: 50%;width: 50%;background: beige;float: left;text-align: center;font-size: 30px">磁盤使用狀況</div> <div id="chart_io" style="height: 50%;width: 50%;background:white;float: right;text-align: center;font-size: 30px" >硬盤/網絡IO狀況</div> </div>
5.總結
按照上述提到的知識點就能夠搭建出來一個本身的監控系統。須要完善的就是highcharts的顯示問題了,這個能夠參考下相關資料設置成本身滿意的顯示方式。
固然,因爲本人水平有限,其中不免有不足的地方,歡迎你們提出來,多作交流,共同進步。