【Ovirt 笔记】仪表板的实现分析与整理
2018-08-22 本文已影响1人
58bc06151329
文前说明
作为码农中的一员,需要不断的学习,我工作之余将一些分析总结和学习笔记写成博客与大家一起交流,也希望采用这种方式记录自己的学习之旅。
本文仅供学习交流使用,侵权必删。
不用于商业目的,转载请注明出处。
分析整理的版本为 Ovirt 4.2.3 版本。
-
ovirt-engine 中的仪表板通过插件的方式加载到管理界面中。具体的实现方式可以参考 Plugin 的实现分析与整理 。
- 3.4.5 的插件通过 AngularJS 实现。
- 4.2.3 的插件通过 React 实现。源码下载地址(https://github.com/oVirt/ovirt-engine-dashboard)
- 将插件加载到管理界面都采用了同样的机制,其中都用到了 JSNI 实现 Java 与 Javascript 的互相方法调用。
-
PluginDefinitions
类实现了插件配置文件的信息读取(仪表板的配置信息文件所在位置 /usr/share/ovirt-engine/ui-plugins/dashboard.json)。
[root@localhost ui-plugins]# cat dashboard.json
{
"name": "dashboard",
"url": "plugin/dashboard/plugin.html",
"resourcePath": "dashboard-resources",
"lazyLoad": false
}
属性 | 说明 |
---|---|
name | 插件名称 |
url | 插件入口地址 |
resourcePath | 资源路径 |
lazyLoad | 是否延迟加载 |
-
PluginManager
类实现了插件数据信息的读取,同时通过exposePluginApi
方法调用 Javascript 创建了 pluginApi 对象。- 用来作为 Java 方法与 Javascript 方法互相调用的桥梁。
1. 调用实现
- 仪表板插件项目中使用了 HtmlWebpackPlugin,可以根据指定的模板生成 html。
配置属性 | 说明 |
---|---|
title | 生成 html 文件的标题。 |
filename | html 文件的文件名,默认是 index.html。 |
template | 模板名称,模板类型可以是 html、jade、ejs 等。 |
inject | true(默认值,script 标签在 html 文件的 body 底部)、body( script 标签在 html 文件的 body 底部)、head(script 标签在 html 文件的 head 中)、false(不插入生成的 js 文件) |
favicon | 生成一个 favicon ,值是一个路径。 |
minify | 使用 minify 对生成的 html 文件进行压缩。默认是 false。 |
cache | 默认 true,内容变化的时候生成一个新的文件。 |
showErrors | webpack 报错时,把错误信息包裹在一个 pre 中,默认是 true。 |
chunks | 当有多个入口文件,编译后生成多个打包后的文件,那么 chunks 能选择使用哪些 js 文件。默认全部显示。 |
excludeChunks | 排除掉一些 js。 |
xhtml | 是否兼容 xhtml 模式引用文件。默认值是 false。 |
chunksSortMode | script 的顺序,none、auto、dependency、 {function} |
- 使用 plugin.template.ejs 模板文件生成 plugin.html 入口文件。
new HtmlWebpackPlugin({
filename: 'main-tab.html',
template: 'static/html/main-tab.template.ejs',
inject: true,
chunks: ['vendor', 'main-tab']
}),
new HtmlWebpackPlugin({
filename: 'plugin.html',
template: 'static/html/plugin.template.ejs',
inject: true,
chunks: ['vendor', 'plugin']
}),
[root@localhost dashboard-resources]# cat plugin.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<script type="text/javascript" src="/ovirt-engine/webadmin/plugin/dashboard/js/plugin.db9ef782.js"></script></body>
</html>
- 加载 plugin.js 文件。
- 注册实现加载仪表板选项卡中的内容。
- priority 为选项卡显示顺序,-1 排在最上面。
getPluginApi().register({
UiInit () {
// add Dashboard main tab
getPluginApi().addPrimaryMenuPlace(msg.mainTabTitle(), dashboardPlaceToken, `${pluginBasePath}/main-tab.html`, {
// position this tab before any standard ones
priority: -1,
// customize the prefix displayed in search bar
searchPrefix: 'Dashboard',
defaultPlace: true,
icon: 'fa-tachometer'
})
}
})
-
getPluginApi
在 plugin-api.js 文件中定义。-
pluginApi 对象在
PluginManager
类的exposePluginApi
方法中已经实现。
-
pluginApi 对象在
const getPluginApi = () => {
api = api || getWebAdminWindow().pluginApi(pluginName)
return api
}
pluginApi.fn = pluginApi.prototype = {
pluginName: null, // Initialized in constructor function
// Constructor function
init: function(pluginName) {
this.pluginName = pluginName;
return this;
},
......
// Give init function the pluginApi prototype for later instantiation
pluginApi.fn.init.prototype = pluginApi.fn;
-
exposePluginApi
方法中实现了ready
方法,实际调用了PluginManager
类的pluginReady
方法。- 最终执行的是
getPluginApi
的UiInit
方法。
- 最终执行的是
// Indicates that the plugin is ready for use
ready: function() {
ctx.@org.ovirt.engine.ui.webadmin.plugin.PluginManager::pluginReady(Ljava/lang/String;)(this.pluginName);
},
// Initialize the plugin once it's ready
initPlugin(plugin);
if (invokePlugin(plugin, "UiInit", null)) { //$NON-NLS-1$
-
UiInit
方法中,加载了 main-tab.html 页面,界面中加载了 main-tab.jsx。-
main-tab.jsx 文件中加载了
- DashboardDataProvider 数据源文件
- GlobalDashboard 界面展示文件。
-
main-tab.jsx 文件中加载了
import DashboardDataProvider from './components/DashboardDataProvider'
import GlobalDashboard from './components/GlobalDashboard'
1.1 文件说明
1.1.1 DashboardDataProvider
- 包含数据源配置对象。
- 通过调用 servlet 获取展示数据。
_fetchData () {
const request = this._jqXHR = $.ajax({
method: 'GET',
url: `${getPluginApi().engineBaseUrl()}webadmin/dashboard_data`,
dataType: 'json',
headers: {
'Accept': 'application/json'
// For testing purposes you can uncomment either of these.
// 'Prefer': 'fake_data' // returns randomly generated data
// 'Prefer': 'error' // triggers HTTP error response
}
})
request.done((data) => {
this._updateData({ data: this._transformData({ data }) })
})
request.fail(() => {
console.error('Request failed', request)
this._updateData({ data: DATA_ERROR })
})
}
<servlet>
<servlet-name>dashboardData</servlet-name>
<servlet-class>org.ovirt.engine.ui.frontend.server.dashboard.DashboardDataServlet</servlet-class>
<load-on-startup>100</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dashboardData</servlet-name>
<url-pattern>/dashboard_data</url-pattern>
</servlet-mapping>
-
DashboardDataServlet 展示数据获取对象。
- 生成两个调度,定时更新数据缓存。
/*
* Update the utilization cache now and every 5 minutes (by default) thereafter, but never run 2 updates simultaneously.
*/
try {
UTILIZATION_CACHE_UPDATE_INTERVAL = config.getLong(UTILIZATION_CACHE_UPDATE_INTERVAL_KEY);
} catch (IllegalArgumentException e) {
log.error("Missing/Invalid key \"{}\", using default value of 300", UTILIZATION_CACHE_UPDATE_INTERVAL_KEY, e); //$NON-NLS-1$
UTILIZATION_CACHE_UPDATE_INTERVAL = 300;
}
utilizationCacheUpdate = scheduledExecutor.scheduleWithFixedDelay(new Runnable() {
Logger log = LoggerFactory.getLogger(DashboardDataServlet.class.getName() + ".CacheUpdate.Utilization"); //$NON-NLS-1$
@Override
public void run() {
log.trace("Attempting to update the Utilization cache"); //$NON-NLS-1$
try {
populateUtilizationCache();
} catch (DashboardDataException e) {
log.error("Could not update the Utilization Cache: {}", e.getMessage(), e); //$NON-NLS-1$
}
}
}, 0, UTILIZATION_CACHE_UPDATE_INTERVAL, TimeUnit.SECONDS);
log.info("Dashboard utilization cache updater initialized (update interval {}s)", UTILIZATION_CACHE_UPDATE_INTERVAL); //$NON-NLS-1$
try {
INVENTORY_CACHE_UPDATE_INTERVAL = config.getLong(INVENTORY_CACHE_UPDATE_INTERVAL_KEY);
} catch (IllegalArgumentException e) {
log.error("Missing/Invalid key \"{}\", using default value of 60", INVENTORY_CACHE_UPDATE_INTERVAL_KEY, e); //$NON-NLS-1$
INVENTORY_CACHE_UPDATE_INTERVAL = 60;
}
inventoryCacheUpdate = scheduledExecutor.scheduleWithFixedDelay(new Runnable() {
Logger log = LoggerFactory.getLogger(DashboardDataServlet.class.getName() + ".CacheUpdate.Inventory"); //$NON-NLS-1$
@Override
public void run() {
log.trace("Attempting to update the Inventory cache"); //$NON-NLS-1$
try {
populateInventoryCache();
} catch (DashboardDataException e) {
log.error("Could not update the Inventory Cache: {}", e.getMessage(), e); //$NON-NLS-1$
}
}
}, 0, INVENTORY_CACHE_UPDATE_INTERVAL, TimeUnit.SECONDS);
log.info("Dashboard inventory cache updater initialized (update interval {}s)", INVENTORY_CACHE_UPDATE_INTERVAL); //$NON-NLS-1$
1.1.1.1 获取数据源连接
@Resource(mappedName = "java:/DWHDataSource")
private DataSource dwhDataSource;
@Resource(mappedName = "java:/ENGINEDataSource")
private DataSource engineDataSource;
- 根据资源文件配置查询 SQL
资源文件名称 | 说明 |
---|---|
ClusterDwhDAO.properties | dwh 库中集群相关统计 SQL。 |
ClusterEngineDAO.properties | engine 库中集群相关统计 SQL。 |
GlusterVolumeEngineDAO.properties | engine 库中卷相关统计 SQL。 |
HostDwhDAO.properties | dwh 库中主机相关统计 SQL。 |
HostEngineDAO.properties | engine 库中主机相关统计 SQL。 |
StorageDomainDwhDAO.properties | dwh 库中存储域相关统计 SQL。 |
StorageDomainEngineDAO.properties | engine 库中存储域相关统计 SQL。 |
VmDwhDAO.properties | dwh 库中虚拟机相关统计 SQL。 |
VmEngineDAO.properties | engine 库中虚拟机相关统计 SQL。 |
host.hourly_cpu_mem_history=SELECT \
the_datetime AS the_date, \
SUM(a.cpu_usage_per_host) / SUM(a.total_host_cpu_cores) AS cpu_avg, \
SUM(a.memory_usage_per_host) / SUM(a.total_host_mem_avg) AS mem_avg \
FROM \
( \
SELECT \
date_trunc('hour',hourly.history_datetime) AS the_date, \
hosts.host_id, \
AVG(COALESCE(hourly.cpu_usage_percent, 0) * number_of_cores ) AS cpu_usage_per_host, \
AVG(COALESCE(hourly.memory_usage_percent, 0) * memory_size_mb ) AS memory_usage_per_host , \
AVG(COALESCE (hosts.number_of_cores , 0 )) AS total_host_cpu_cores, \
AVG(COALESCE (hosts.memory_size_mb , 0 ) )AS total_host_mem_avg \
FROM \
v4_2_statistics_hosts_resources_usage_hourly hourly \
INNER JOIN \
v4_2_configuration_history_hosts hosts \
ON \
hosts.host_id = hourly.host_id \
WHERE \
/*Here we filter by active hosts only*/ \
hourly.host_status = 1 AND \
/*Here we join the configrations of the hosts with the statistics*/ \
hourly.host_configuration_version = hosts.history_id AND \
/*Here we filter by the last 24 hours period*/ \
history_datetime >= date_trunc('hour',CURRENT_TIMESTAMP) - INTERVAL '24 hours' AND \
history_datetime <= date_trunc('hour',CURRENT_TIMESTAMP) + INTERVAL '2 hours' \
GROUP BY \
hourly.history_datetime, hosts.host_id \
......
1.1.1.2 库存信息
- 数据中心库存信息
private static final String DC_INVENTORY = "datacenter.inventory"; //$NON-NLS-1$
public static InventoryStatus getDcInventoryStatus(DataSource engineDataSource) throws DashboardDataException {
DataCenterDao dao = new DataCenterDao(engineDataSource);
return dao.getDcInventoryStatus();
}
- 集群库存信息
private static final String CLUSTER_INVENTORY = "cluster.inventory"; //$NON-NLS-1$
public static InventoryStatus getClusterInventoryStatus(DataSource engineDataSource) throws DashboardDataException {
ClusterEngineDao dao = new ClusterEngineDao(engineDataSource);
return dao.getClusterInventorySummary();
}
- 主机库存信息
private static final String HOST_INVENTORY = "host.inventory"; //$NON-NLS-1$
public static InventoryStatus getHostInventoryStatus(DataSource engineDataSource) throws DashboardDataException {
HostEngineDao dao = new HostEngineDao(engineDataSource);
return dao.getHostInventoryStatus();
}
- 存储库存信息
private static final String STORAGE_INVENTORY = "storage.inventory"; //$NON-NLS-1$
public static InventoryStatus getStorageInventoryStatus(DataSource engineDataSource) throws DashboardDataException {
StorageDomainEngineDao dao = new StorageDomainEngineDao(engineDataSource);
return dao.getStorageInventoryStatus();
}
- 虚拟机库存信息
private static final String VM_INVENTORY = "vm.inventory"; //$NON-NLS-1$
public static InventoryStatus getVmInventorySummary(DataSource engineDataSource) throws DashboardDataException {
VmEngineDao dao = new VmEngineDao(engineDataSource);
return dao.getVmInventoryStatus();
}
- 卷库存信息
private static final String GLUSTER_VOLUME_INVENTORY = "glusterVolume.inventory"; //$NON-NLS-1$
public static InventoryStatus getGlusterVolumeInventorySummary(DataSource engineDataSource)
throws DashboardDataException {
GlusterVolumeEngineDao dao = new GlusterVolumeEngineDao(engineDataSource);
return dao.getVolumeInventoryStatus();
}
1.1.1.3 全局利用率
1.1.1.3.1 CPU 和内存信息
HourlySummaryHelper.getCpuMemSummary(utilization, dwhDataSource);
- CPU 和内存总量
private static final String TOTAL_CPU_MEMORY_COUNT = "host.total_cpu_memory_count"; //$NON-NLS-1$
private static void getTotalCpuMemCount(GlobalUtilizationResourceSummary cpuSummary,
GlobalUtilizationResourceSummary memSummary, DataSource dwhDataSource) throws DashboardDataException {
HostDwhDao dao = new HostDwhDao(dwhDataSource);
ResourcesTotal total = dao.getTotalCpuMemCount();
cpuSummary.setPhysicalTotal(total.getCpuTotal());
//Transform MB to GB.
memSummary.setPhysicalTotal(total.getMemTotal() / 1024);
}
- CPU 和内存小时使用率
private static final String HOURLY_CPU_MEM_HISTORY = "host.hourly_cpu_mem_history"; //$NON-NLS-1$
private static void getHourlyCpuMemUsage(GlobalUtilizationResourceSummary cpuSummary,
GlobalUtilizationResourceSummary memSummary, DataSource dataSource) throws DashboardDataException {
List<HistoryNode> cpuHistory = new ArrayList<>();
List<HistoryNode> memHistory = new ArrayList<>();
HostDwhDao dao = new HostDwhDao(dataSource);
List<ResourceUsage> history = dao.getHourlyCpuMemUsage();
for (ResourceUsage item: history) {
cpuHistory.add(new HistoryNode(item.getEpoch(), item.getCpuValue()));
memHistory.add(new HistoryNode(item.getEpoch(), item.getMemValue() * memSummary.getTotal() / 100));
}
ResourceUsage last5minUsage = dao.getLast5MinCpuMemUsage();
cpuSummary.setUsed(last5minUsage.getCpuValue());
memSummary.setUsed(last5minUsage.getMemValue() * memSummary.getTotal() / 100);
cpuSummary.setHistory(cpuHistory);
memSummary.setHistory(memHistory);
}
- 虚拟 CPU 和内存总量
private static final String VIRTUAL_CPU_MEMORY_COUNT = "vm.virtual_cpu_memory_count"; //$NON-NLS-1$
private static void getVirtualCpuMemCount(GlobalUtilizationResourceSummary cpuSummary,
GlobalUtilizationResourceSummary memSummary, DataSource dwhDataSource) throws DashboardDataException {
VmDwhDao dao = new VmDwhDao(dwhDataSource);
ResourcesTotal resourcesTotal = dao.getVirtualCpuMemCount();
cpuSummary.setVirtualTotal(resourcesTotal.getCpuTotal());
cpuSummary.setVirtualUsed(resourcesTotal.getCpuUsed());
memSummary.setVirtualTotal(resourcesTotal.getMemTotal());
memSummary.setVirtualUsed(resourcesTotal.getMemUsed());
}
1.1.1.3.2 存储信息
utilization.setStorage(HourlySummaryHelper.getStorageSummary(dwhDataSource));
- 存储总量
private static final String TOTAL_STORAGE_COUNT = "storage.total_count"; //$NON-NLS-1$
private static Double getTotalStorageCount(DataSource dwhDataSource) throws DashboardDataException {
StorageDomainDwhDao dao = new StorageDomainDwhDao(dwhDataSource);
//Transform GB to TB.
return dao.getTotalStorageCount() / 1024;
}
- 存储小时使用率
private static final String HOURLY_STORAGE_HISTORY = "storage.hourly_history"; //$NON-NLS-1$
private static List<HistoryNode> getHourlyStorageHistory(DataSource dwhDataSource) throws DashboardDataException {
List<HistoryNode> history = new ArrayList<>();
StorageDomainDwhDao dao = new StorageDomainDwhDao(dwhDataSource);
List<ResourceUsage> usageList = dao.getHourlyStorageHistory();
for (ResourceUsage usage: usageList) {
//Transform GB to TB.
history.add(new HistoryNode(usage.getEpoch(), usage.getStorageValue() / 1024));
}
return history;
}
- 存储最后 5 分钟使用平均值
private static final String LAST5_MIN_STORAGE_AVERAGE = "storage.last5_minutes_average"; //$NON-NLS-1$
private static double getLast5MinutesStorageAverage(DataSource dwhDataSource) throws DashboardDataException {
StorageDomainDwhDao dao = new StorageDomainDwhDao(dwhDataSource);
//Transform GB to TB.
return dao.getLast5MinutesStorageAverage() / 1024;
}
1.1.1.4 集群利用率
- CPU 和内存最后 24 小时使用率
private static final String CLUSTER_LAST_24_AVERAGE = "cluster.last24hours"; //$NON-NLS-1$
public static void getCpuAndMemory(HeatMapData utilization, DataSource dataSource) throws DashboardDataException {
ClusterDwhDao dao = new ClusterDwhDao(dataSource);
List<ClusterResourceAverage> averages = dao.getClusterCpuAndMemoryAverage();
List<HeatMapBlock> cpu = new ArrayList<>();
List<HeatMapBlock> memory = new ArrayList<>();
for (ClusterResourceAverage data: averages) {
cpu.add(new HeatMapBlock(data.getName(), data.getCpuAverage()));
memory.add(new HeatMapBlock(data.getName(), data.getMemoryAverage()));
}
utilization.setCpu(cpu);
utilization.setMemory(memory);
}
1.1.1.5 存储利用率
- 存储最后 24 小时使用率
private static final String STORAGE_LAST24_AVERAGE = "storage.last24hours_average"; //$NON-NLS-1$
public static List<HeatMapBlock> getStorage(DataSource dwhDataSource) throws DashboardDataException {
List<HeatMapBlock> nodes = new ArrayList<>();
StorageDomainDwhDao dao = new StorageDomainDwhDao(dwhDataSource);
for (StorageDomainAverage data: dao.getStorageAverage()) {
nodes.add(new HeatMapBlock(data.getName(), data.getValue()));
}
return nodes;
}
1.1.2 GlobalDashboard
- 包含界面展示控件
- 将获取的数据渲染到界面上。
import RefreshDataControl from './RefreshDataControl'
import LastUpdatedLabel from './LastUpdatedLabel'
import AggregateStatusCard from './AggregateStatusCard'
import UtilizationTrendCard from './UtilizationTrendCard'
import HeatMapLegend from './patternfly/HeatMapLegend'
import HeightMatching from './helper/HeightMatching'
<div className='container-fluid container-tiles-pf containers-dashboard'>
{/* refresh buttons and last updated information label */}
<div className='row row-tile-pf'>
<div className='col-xs-12 global-dashboard-update-column'>
<RefreshDataControl onRefresh={onRefreshData} />
<div style={{ marginLeft: 10 }}>
<LastUpdatedLabel date={lastUpdated} />
</div>
</div>
</div>
{/* inventory cards - match height of all of the card's titles and body */}
<HeightMatching
className={classNames('row', 'row-tile-pf', {'seven-cols': showGlusterCard})}
selector={[
'.card-pf-aggregate-status .card-pf-title',
'.card-pf-aggregate-status .card-pf-body' ]}>
<div className={statusCardClass}>
<AggregateStatusCard
data={inventory.dc}
title={msg.statusCardDataCenterTitle()}
mainIconClass='fa fa-building-o'
onTotalCountClick={() => {
applySearch(webadminPlaces.dc, searchPrefixes.dc)
}}
onStatusCountClick={(statusItem) => {
applySearch(webadminPlaces.dc, searchPrefixes.dc, [{
name: searchFields.status,
values: statusItem.statusValues
}])
}} />
</div>
......