前言:此功能代码不代表笔者代码正常水平。。。真的只是为了实现而实现,很多硬代码加不优雅的地方。就让我自己先吐槽一下吧,首先是代码每次会去拿token而不是用horizon缓存的token,没有将js,css优雅的放进horizon,以及panel的名字都没改,最后就是前端布局不好看(这个真的是当下能力之外的了)。
首先说说需求,在horizon有这么一个页面,可以查看本项目的虚拟机的一些基本的性能指标,比如cpu使用率,内存使用率,磁盘io,网络io等。
怎么实现呢?
数据获取:
方案一,自己造轮子,写个agent到计算节点,然后起个服务作为服务端,然后horizon交互服务端。
方案二,用zabbix,写个plugin,zabbix存,horizon与zabbix交互。
方案三,horizon与ceilometer交互。
页面数据展示:
方案一:highcharts
方案二:echarts
方案三:horizon的js库。
笔者的数据取自ceilometer,数据展示选择highcharts。
注:ceilometer众所周知是有性能问题的,解决方案是gnocchi。
主题内容如下:
一:Ceilometer API
二:Highcharts API
三:组织代码
(一)Ceilometer API
CPU使用率:/v2/meters/cpu_util
内存使用量:/v2/meters/memory.resident
磁盘io:/v2/meters/disk.read.bytes.rate,/v2/meters/disk.write.bytes.rate
网络io:/network.incoming.bytes.rate, network.outgoing.bytes.rate
(二)Highcharts
<script language="JavaScript">
$(document).ready(function() {
var chart = {
zoomType: 'x'
};
var credits = {
text: 'UMCloud',
href: 'http://www.umcloud.com/'
};
var title = {
text: "`hn`: {{ ret.0.title }}"
};
var xAxis = {
type: 'datetime',
labels: {
step: 1,
formatter: function () {
return Highcharts.dateFormat('%Y-%m-%d %H:%M', this.value );
}
}};
var yAxis = {
title: {
text: "单位: {{ ret.0.unit }}"
},
labels: {
formatter: function () {
return this.value;
}
}
};
var tooltip = {
valueSuffix: '{{ ret.0.unit }}',
headerFormat: '{point.x:%Y-%m-%d %H:%M}<br>',
pointFormat: "{series.name}: {point.y:.3f}"
}
var legend = {
enabled: false
//layout: 'vertical',
// align: 'right',
//verticalAlign: 'middle',
// borderWidth: 0
};
var exporting = {
csv: {
dateFormat: '%Y-%m-%d %H:%M'
}
};
var setOptions = {
global:
{ useUTC: false }
};
var series = {{ ret.1 }} ;
var json = {};
json.title = title;
json.chart = chart;
json.xAxis = xAxis;
json.yAxis = yAxis;
json.tooltip = tooltip;
json.legend = legend;
json.series = series;
json.credits = credits;
json.setOptions = setOptions;
$('#containerP').highcharts(json);
});
$('#tableid').DataTable();
</script>
(三)组织代码
#coding: utf-8
import sys
import requests
import time
import logging
import os
import subprocess as sp
from ConfigParser import ConfigParser
from pprint import pprint
import datetime
import json
confFile="/etc/unit.conf"
logfile = "/var/log/selfm.log"
debug=False
level = logging.WARNING if not debug else logging.DEBUG
logging.basicConfig(level=level,
format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
datefmt='%a, %d %b %Y %H:%M:%S',
filename=logfile,
filemode='a')
def tons(dateStr):
"""convert the isodateformate str to unix timestamp"""
try:
d = datetime.datetime.strptime(dateStr,'%Y-%m-%dT%H:%M:%S.%f')
except Exception as e:
d = datetime.datetime.strptime(dateStr,'%Y-%m-%dT%H:%M:%S')
d = d + datetime.timedelta(hours=8)
ret = d.strftime("%s000")
return int(ret)
class baseInfo(object):
"""init the info of API, and get the token for access the api"""
def __init__(self, token=None):
headers = {}
headers["Content-Type"] = "application/json"
self.headers = headers
self.cf = ConfigParser()
self.cf.read(confFile)
self.conf = self.getConf()
self.catalog, self.token = self.getToken()
self.url = [url for url in self.catalog if url["name"] == "ceilometer"]
self.url = self.url[0]["endpoints"][0]["publicURL"]
def getConf(self):
try:
conf = {
"url": self.cf.get("ser","OS_AUTH_URL"),
"uname" : self.cf.get("ser","OS_USERNAME"),
"passwd" : self.cf.get("ser","OS_PASSWORD"),
"tname" : self.cf.get("ser","OS_TENANT_NAME")}
except Exception as e:
logging.critical("加载配置文件失败")
logging.critical(e)
return conf
def getToken(self):
headers = self.headers
url = self.conf["url"] + "/tokens"
data = '{"auth": {"tenantName": "%s", "passwordCredentials": {"username": "%s", "password": "%s"}}}'
data = data % (self.conf["tname"], self.conf["uname"], self.conf["passwd"])
try:
logging.debug("开始获取Token")
ret = requests.post(url, data=data, headers=headers)
#print ret.url
logging.debug("request url:%s" % ret.url)
ret = ret.json()
except Exception as e:
msg = "获取Token失败 data:%s headers:%s" % (data, headers)
logging.critical(msg)
logging.critical(e)
catalog = ret["access"]["serviceCatalog"]
token = ret["access"]["token"]["id"]
return catalog, token
def getCResp(self, suffix, method, data=None, headers=None, params=None, isjson=True):
"""return the result of ceilometer response"""
url = self.url + suffix
if headers == None:
headers = self.headers.copy()
headers["X-Auth-Token"] = self.token
req = getattr(requests, method)
try:
ret = req(url, data=data, headers=headers, params=params, verify=False)
#print ret.url
logging.debug("request url:%s" % ret.url)
except Exception as e:
msg = "%s访问%s失败 data:%s headers:%s" % (method, suffix, data, headers)
logging.critical(msg)
logging.critical(e)
sys.exit(1)
if ret.status_code == 401:
self.catalog, self.token = self.getToken()
headers["X-Auth-Token"] = self.token
ret = req(url, data=data, headers=headers)
if isjson:
ret = ret.json()
return ret
class ceil(baseInfo):
"""the class for grab ceilometer metric"""
def __init__(self, vm, timeRange):
super(ceil, self).__init__()
self.timeRange = timeRange
self.vm = vm
self.qge = "&q.field=timestamp&q.op=lt&q.value=%s" % self.timeRange[0]
self.qlt = "&q.field=timestamp&q.op=ge&q.value=%s" % self.timeRange[1]
self.qr = "?q.field=resource_id&q.op=eq&q.value=%s" % self.vm
def getData(self, suffix, plotype, title):
data = []
options = {}
for s in suffix:
s2 = "".join([s, self.qr, self.qge, self.qlt, '&limit=10000'])
#print s2
resp = self.getCResp(s2, "get")
if resp:
if s in ["/v2/meters/disk.read.bytes.rate", "/v2/meters/disk.write.bytes.rate"]:
volumes = [[tons(i["recorded_at"]), i["counter_volume"] / 1024 ] for i in resp ]
unit = "KB/s"
name = s.split("/")[-1]
else:
volumes = [[tons(i["recorded_at"]), i["counter_volume"]] for i in resp ]
unit = resp[1]["counter_unit"]
name = s.split("/")[-1]
seq = {"type": plotype,
"name": name,
"data": volumes}
data.append(seq)
else:
unit = "None"
title = "No Data"
options["unit"] = unit
options["title"] = title
ret = [options, data]
return ret
def cpu(self, plotype="line", title="cpu_util"):
"""return the data series of highchart need
ret = [options, data]
options["unit"] = "None"
options["title"] = "No Data"
data = [{type:plottype,name:name,data:[nstime, datapoint]}]
"""
suffix = ["/v2/meters/cpu_util"]
ret = self.getData(suffix, plotype, title)
return ret
def disk(self, plotype="line", title="diskio"):
suffix = ["/v2/meters/disk.read.bytes.rate", "/v2/meters/disk.write.bytes.rate"]
ret = self.getData(suffix, plotype, title)
return ret
def mem(self, plotype="line", title="memory_usage"):
suffix = ["/v2/meters/memory.resident"]
#suffix = ["/v2/meters/memory.resident", "/v2/meters/memory"]
ret = self.getData(suffix, plotype, title)
return ret
def net(self, plotype="line"):
suffix = "/v2/query/samples"
meters = ["network.incoming.bytes.rate", "network.outgoing.bytes.rate"]
options = {}
data = []
kb = 1024
mb = 1024 * 1024
for m in meters:
d = {
"filter": "{\"and\": [{\"=\": {\"counter_name\": \"%s\"}}, {\"=~\": {\"resource_id\":\".*%s.*\"}}, {\"<\": {\"timestamp\": \"%s\"}}, {\">\": {\"timestamp\": \"%s\"}}]}" % (m, self.vm, self.timeRange[0], self.timeRange[1])
}
#print d
resp = self.getCResp(suffix, "post", data=json.dumps(d))
if resp:
volumes = [[tons(i["recorded_at"]), i["volume"] / 1024 ] for i in resp ]
unit = "KB/s"
name = m
seq = {"type": plotype,
"name": name[8:16],
"data": volumes}
data.append(seq)
else:
unit = "None"
title = "No Data"
options["unit"] = unit
options["title"] = "netio"
ret = [options, data]
return ret
def test():
now = datetime.datetime.utcnow()
yesterday = now + datetime.timedelta(hours=-2)
now = now.isoformat()
print now
yesterday = yesterday.isoformat()
c = ceil("8fc2a76e-90c5-491e-b207-28e90d5a5fab", (now, yesterday))
pprint(c.net())
#pprint(c.net())
if __name__ == "__main__":
test()
总结
因为没有很技巧的部分,所以写着并不是有多少激情,只是实现了功能而已。需要花点时间的就是查询API,以及horizon的一些框架结构。
注:调试rest API超级推荐postman这个应用。
附图:
相关链接:
http://docs.openstack.org/admin-guide/telemetry-measurements.html
https://docs.openstack.org/developer/ceilometer/webapi/v2.html
http://api.highcharts.com/highcharts
来源:oschina
链接:https://my.oschina.net/u/4343232/blog/4344254