美文网首页程序员Ovirt虚拟化技术
【Ovirt 笔记】仪表板的实现分析与整理

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

作者: 58bc06151329 | 来源:发表于2018-08-22 17:12 被阅读1次

    文前说明

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

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

    分析整理的版本为 Ovirt 4.2.3 版本。

    • ovirt-engine 中的仪表板通过插件的方式加载到管理界面中。具体的实现方式可以参考 Plugin 的实现分析与整理

    • 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'
          })
      }
    
    })
    
    • 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() {
          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 界面展示文件。
    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>
    ......
    

    相关文章

      网友评论

        本文标题:【Ovirt 笔记】仪表板的实现分析与整理

        本文链接:https://www.haomeiwen.com/subject/fmgeiftx.html