view src/app/components/jvm-info/jvm-memory/jvm-memory.controller.js @ 267:8fea191dbff0

Refactor and clean up jvm-memory Reviewed-by: jkang Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2017-October/025371.html
author Andrew Azores <aazores@redhat.com>
date Wed, 11 Oct 2017 14:59:09 -0400
parents 36e99edc6d66
children 597d7d7e0aff
line wrap: on
line source

/**
 * Copyright 2012-2017 Red Hat, Inc.
 *
 * Thermostat is distributed under the GNU General Public License,
 * version 2 or any later version (with a special exception described
 * below, commonly known as the "Classpath Exception").
 *
 * A copy of GNU General Public License (GPL) is included in this
 * distribution, in the file COPYING.
 *
 * Linking Thermostat code with other modules is making a combined work
 * based on Thermostat.  Thus, the terms and conditions of the GPL
 * cover the whole combination.
 *
 * As a special exception, the copyright holders of Thermostat give you
 * permission to link this code with independent modules to produce an
 * executable, regardless of the license terms of these independent
 * modules, and to copy and distribute the resulting executable under
 * terms of your choice, provided that you also meet, for each linked
 * independent module, the terms and conditions of the license of that
 * module.  An independent module is a module which is not derived from
 * or based on Thermostat code.  If you modify Thermostat, you may
 * extend this exception to your version of the software, but you are
 * not obligated to do so.  If you do not wish to do so, delete this
 * exception statement from your version.
 */

import 'c3';
import _ from 'lodash';
import services from 'shared/services/services.module.js';
import filters from 'shared/filters/filters.module.js';
import service from './jvm-memory.service.js';

class JvmMemoryController {
  constructor ($stateParams, $interval, jvmMemoryService, metricToBigIntFilter,
    bigIntToStringFilter, stringToNumberFilter, dateFilter, DATE_FORMAT,
    metricToNumberFilter, scaleBytesService,
    sanitizeService, $translate) {
    'ngInject';

    this.jvmId = $stateParams.jvmId;
    this._interval = $interval;
    this._jvmMemoryService = jvmMemoryService;
    this._sanitizeService = sanitizeService;
    this._translate = $translate;

    this._metricToBigInt = metricToBigIntFilter;
    this._bigIntToString = bigIntToStringFilter;
    this._stringToNumber = stringToNumberFilter;
    this._dateFilter = dateFilter;
    this._dateFormat = DATE_FORMAT;
    this._metricToNumber = metricToNumberFilter;
    this._scaleBytes = scaleBytesService;

    this.metaspaceSnapshotData = {
      used: 0,
      total: 0
    };
    this._metaspaceData = {
      timestamps: [],
      used: [],
      capacity: []
    };
    this.metaspaceSeriesData = {
      xData: ['timestamp'],
      yData: ['memory']
    };
    this.metaspaceBarConfig = {
      units: 'B'
    };

    $translate(['jvmMemory.X_AXIS_LABEL', 'jvmMemory.Y_AXIS_LABEL']).then(translations => {
      this._xAxisLabel = translations['jvmMemory.X_AXIS_LABEL'];
      this._yAxisLabel = translations['jvmMemory.Y_AXIS_LABEL'];
      this.metaspaceLineConfig = {
        chartId: 'metaspaceLineChart',
        units: 'B',
        axis: {
          x: {
            show: true,
            type: 'timeseries',
            label: {
              text: this._xAxisLabel,
              position: 'outer-center'
            },
            tick : {
              format: timestamp => this._dateFilter(timestamp, this._dateFormat.time.medium),
              count: 5,
              fit: false
            }
          },
          y: {
            show: true,
            min: 0,
            padding: 0,
            tick: 10,
            label: {
              text: this._yAxisLabel,
              position: 'outer-middle'
            }
          }
        }
      };
    });
    this.spaceBarConfigs = [];
    this._generationData = new Map();
    this.generationSnapshotData = {};
    this.generationSeriesData = {};
    this.generationLineConfigs = new Map();

    this._refreshRate = 2000;
  }

  getLineConfig (genIndex, spaceIndex) {
    let key = `line-gen-${genIndex}-space-${spaceIndex}`;
    if (!this.generationLineConfigs.has(key)) {
      let config = {
        chartId: key,
        units: 'B',
        axis: {
          x: {
            show: true,
            type: 'timeseries',
            label: {
              text: this._xAxisLabel,
              position: 'outer-center'
            },
            tick : {
              format: timestamp => this._dateFilter(timestamp, this._dateFormat.time.medium),
              count: 5,
              fit: false
            }
          },
          y: {
            show: true,
            min: 0,
            padding: 0,
            tick: 10,
            label: {
              text: this._yAxisLabel,
              position: 'outer-middle'
            }
          }
        }
      };
      this.generationLineConfigs.set(key, config);
    }
    return this.generationLineConfigs.get(key);
  }

  $onInit () {
    this._start();
  }

  $onDestroy () {
    this._stop();
  }

  _start () {
    this._stop();
    this._update();
    this._refresh = this._interval(() => this._update(), this.refreshRate);
  }

  _stop () {
    if (angular.isDefined(this._refresh)) {
      this._interval.cancel(this._refresh);
      delete this._refresh;
    }
  }

  set refreshRate (val) {
    this._stop();
    this._refreshRate = parseInt(val);
    if (this._refreshRate > 0) {
      this._start();
    }
  }

  get refreshRate () {
    return this._refreshRate.toString();
  }

  multichartMetaspace () {
    return new Promise(resolve =>
      this._jvmMemoryService.getJvmMemory(this.jvmId).then(resp =>
        resolve(this.convertMemStat(resp[0].metaspaceUsed))
      )
    );
  }

  multichartSpace (generationIndex, spaceIndex) {
    return new Promise(resolve =>
      this._jvmMemoryService.getJvmMemory(this.jvmId).then(resp => {
        generationIndex = parseInt(generationIndex);
        spaceIndex = parseInt(spaceIndex);
        let generation = resp[0].generations[generationIndex];
        let space = generation.spaces[spaceIndex];
        resolve(this.convertMemStat(space.used));
      })
    );
  }

  _update () {
    this._jvmMemoryService.getJvmMemory(this.jvmId).then(resp => {
      let keys = [];
      resp.forEach(update => {
        for (let i = 0; i < update.generations.length; i++) {
          update.generations[i].index = i;
        }

        let timestamp = this._metricToNumber(update.timeStamp);

        let metaspaceUsed = this._metricToNumber(update.metaspaceUsed);
        let metaspaceCapacity = this._metricToNumber(update.metaspaceCapacity);

        this._metaspaceData.timestamps.push(timestamp);
        this._metaspaceData.used.push(metaspaceUsed);
        this._metaspaceData.capacity.push(metaspaceCapacity);

        update.generations.forEach(generation => {
          generation.spaces.forEach(space => {
            let key = this._getKey(generation.index, space.index);
            keys.push(key);
            if (!this._generationData.has(key)) {
              this._generationData.set(key, {
                timestamps: [],
                used: [],
                capacity: []
              });
            }

            let used = this._metricToNumber(space.used);
            let capacity = this._metricToNumber(space.capacity);
            let data = this._generationData.get(key);
            data.timestamps.push(timestamp);
            data.used.push(used);
            data.capacity.push(capacity);
          });
        });
      });
      this._updateLineCharts(keys);
      this._updateBarCharts(resp[0]);
    });
  }

  _getKey (genIndex, spaceIndex) {
    return `${genIndex}:${spaceIndex}`;
  }

  _updateLineCharts (keys) {
    this.metaspaceSeriesData = {
      xData: ['timestamp'].concat(this._metaspaceData.timestamps),
      yData: ['memory'].concat(this._metaspaceData.used)
    };

    keys.forEach(key => {
      this.generationSeriesData[key] = {
        xData: ['timestamp'].concat(this._generationData.get(key).timestamps),
        yData: ['memory'].concat(this._generationData.get(key).used)
      };
    });
  }

  _updateBarCharts (data) {
    let metaspaceScale = this._scaleBytes.format(_.last(this._metaspaceData.used));
    this.metaspaceSnapshotData = {
      used: this.convertMemStat(_.last(this._metaspaceData.used), metaspaceScale.scale),
      total: this.convertMemStat(_.last(this._metaspaceData.capacity), metaspaceScale.scale)
    };

    // re-assign chart config so that Angular picks up the change. If we only re-assign property values
    // on the same config object, those config updates are not detected and do not reflect in the charts.
    this.metaspaceBarConfig = {
      chartId: 'metaspaceBarChart',
      units: metaspaceScale.unit
    };

    for (let i = 0; i < data.generations.length; i++) {
      let generation = data.generations[i];
      let gen;
      if (this.generationSnapshotData.hasOwnProperty(i)) {
        gen = this.generationSnapshotData[i];
      } else {
        gen = {
          index: generation.index,
          name: generation.name,
          collector: generation.collector,
          spaces: []
        };
      }
      for (let j = 0; j < generation.spaces.length; j++) {
        let space = generation.spaces[j];
        let key = this._getKey(gen.index, space.index);

        let generationData = this._generationData.get(key);
        let latestTimestamp = _.last(generationData.timestamp);
        let latestUsed = _.last(generationData.used);
        let latestCapacity = _.last(generationData.capacity);

        let genScale = this._scaleBytes.format(latestUsed);
        if (gen.spaces.hasOwnProperty(space.index)) {
          gen.spaces[space.index].used = this.convertMemStat(latestUsed, genScale.scale);
          gen.spaces[space.index].total = this.convertMemStat(latestCapacity, genScale.scale);
        } else {
          gen.spaces[space.index] = {
            index: space.index,
            used: this.convertMemStat(latestUsed, genScale.scale),
            total: this.convertMemStat(latestCapacity, genScale.scale)
          };
        }

        this._translate('jvmMemory.SPACE', { index: space.index }).then(chartTitle => {
          this.spaceBarConfigs[key] = {
            key: key,
            units: genScale.unit,
            title: chartTitle
          };
        });
      }
      this.generationSnapshotData[i] = gen;
    }
  }

  convertMemStat (stat, scale) {
    let bigInt = this._metricToBigInt(stat, scale);
    let str = this._bigIntToString(bigInt);
    let num = this._stringToNumber(str);
    return _.ceil(num);
  }

  sanitize (str) {
    return this._sanitizeService.sanitize(str);
  }

}

export default angular
  .module('jvmMemory.controller', [
    'patternfly',
    'patternfly.charts',
    services,
    filters,
    service
  ])
  .controller('JvmMemoryController', JvmMemoryController)
  .name;