view src/app/components/jvm-info/jvm-memory/jvm-memory.controller.js @ 271:671cfc1d001d

Streamline jvm-memory metric conversion filters Reviewed-by: jkang Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2017-October/025371.html
author Andrew Azores <aazores@redhat.com>
date Wed, 25 Oct 2017 11:51:03 -0400
parents 4023b79e4d58
children b35a283eb016
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, 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._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;
    this._dataAgeLimit = 10 * 60 * 1000;
    this._lastUpdate = this._getCurrentTimestamp();
  }

  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._loadHistoricalData();
    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();
  }

  set dataAgeLimit (val) {
    this._dataAgeLimit = parseInt(val);
    this._loadHistoricalData();
  }

  get dataAgeLimit () {
    return this._dataAgeLimit.toString();
  }

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

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

  _loadHistoricalData () {
    this._clearData();
    let now = this._getCurrentTimestamp();
    let limit = now - this._dataAgeLimit;
    this._jvmMemoryService.getJvmMemory(this.jvmId, limit)
      .then(resp => this._processUpdates(resp));
  }

  _clearData () {
    this._metaspaceData.timestamps = [];
    this._metaspaceData.used = [];
    this._metaspaceData.capacity = [];
    this._generationData.clear();
  }

  _update () {
    this._jvmMemoryService.getJvmMemory(this.jvmId, this._lastUpdate)
      .then(resp => this._processUpdates(resp));
  }

  _processUpdates (resp) {
    this._lastUpdate = this._getCurrentTimestamp();
    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.convertMemStat(update.metaspaceUsed);
      let metaspaceCapacity = this.convertMemStat(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.convertMemStat(space.used);
          let capacity = this.convertMemStat(space.capacity);
          let data = this._generationData.get(key);
          data.timestamps.push(timestamp);
          data.used.push(used);
          data.capacity.push(capacity);
        });
      });
    });
    this._trimData();
    if (resp.length > 0) {
      this._updateLineCharts(keys);
      this._updateBarCharts(resp[0]);
    }
  }

  _trimData () {
    let now = this._getCurrentTimestamp();
    let limit = now - this._dataAgeLimit;

    let metaspaceSpliceCount = 0;
    for (; metaspaceSpliceCount < this._metaspaceData.timestamps.length; metaspaceSpliceCount++) {
      let sample = this._metaspaceData.timestamps[metaspaceSpliceCount];
      if (sample >= limit) {
        break;
      }
    }
    this._metaspaceData.timestamps.splice(0, metaspaceSpliceCount);
    this._metaspaceData.used.splice(0, metaspaceSpliceCount);
    this._metaspaceData.capacity.splice(0, metaspaceSpliceCount);

    this._generationData.forEach(sampleSet => {
      let sampleSpliceCount = 0;
      for (; sampleSpliceCount < sampleSet.timestamps.length; sampleSpliceCount++) {
        let sample = sampleSet.timestamps[sampleSpliceCount];
        if (sample >= limit) {
          break;
        }
      }
      sampleSet.timestamps.splice(0, sampleSpliceCount);
      sampleSet.used.splice(0, sampleSpliceCount);
      sampleSet.capacity.splice(0, sampleSpliceCount);
    });
  }

  _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 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;
    }
  }

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

  convertMemStat (stat, scale) {
    return _.ceil(this._metricToNumber(stat, scale));
  }

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

  _getCurrentTimestamp () {
    return Date.now();
  }

}

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