【Ovirt 笔记】仪表板的实现分析与整理

文前说明

作为码农中的一员,需要不断的学习,我工作之余将一些分析总结和学习笔记写成博客与大家一起交流,也希望采用这种方式记录自己的学习之旅。

本文仅供学习交流使用,侵权必删。
不用于商业目的,转载请注明出处。

分析整理的版本为 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 



  




  • 加载 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'
      })
  }

})
  • getPluginApiplugin-api.js 文件中定义。
    • pluginApi 对象在 PluginManager 类的 exposePluginApi 方法中已经实现。
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 方法。
    • 最终执行的是 getPluginApiUiInit 方法。
// Indicates that the plugin is ready for use
ready: function() {
      [email protected]::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 界面展示文件。
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 })
    })
  }

        dashboardData
        org.ovirt.engine.ui.frontend.server.dashboard.DashboardDataServlet
        100


        dashboardData
        /dashboard_data

  • 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 cpuHistory = new ArrayList<>();
        List memHistory = new ArrayList<>();
        HostDwhDao dao = new HostDwhDao(dataSource);
        List 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 getHourlyStorageHistory(DataSource dwhDataSource) throws DashboardDataException {
        List history = new ArrayList<>();
        StorageDomainDwhDao dao = new StorageDomainDwhDao(dwhDataSource);
        List 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 averages = dao.getClusterCpuAndMemoryAverage();
        List cpu = new ArrayList<>();
        List 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 getStorage(DataSource dwhDataSource) throws DashboardDataException {
        List 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'
{/* refresh buttons and last updated information label */}
{/* inventory cards - match height of all of the card's titles and body */}
{ applySearch(webadminPlaces.dc, searchPrefixes.dc) }} onStatusCountClick={(statusItem) => { applySearch(webadminPlaces.dc, searchPrefixes.dc, [{ name: searchFields.status, values: statusItem.statusValues }]) }} />
......

你可能感兴趣的:(【Ovirt 笔记】仪表板的实现分析与整理)