view src/app/components/jvm-info/jvm-memory/jvm-memory.controller.spec.js @ 268:597d7d7e0aff

Add data age limit to 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 15:38:34 -0400
parents 8fea191dbff0
children 4023b79e4d58
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 controllerModule from './jvm-memory.controller.js';
import filtersModule from 'shared/filters/filters.module.js';
import { default as servicesModule, init as initServices } from 'shared/services/services.module.js';

describe('JvmMemory controller', () => {

  let interval, memSvc, scaleSvc, promise, ctrl, sanitizeSvc, translate, dateFilter, dateFormat;
  beforeEach(() => {
    angular.mock.module(filtersModule);
    angular.mock.module(servicesModule);
    initServices();
    angular.mock.module(controllerModule);
    angular.mock.inject($controller => {
      'ngInject';

      interval = sinon.stub().returns('interval-sentinel');
      interval.cancel = sinon.spy();

      promise = {
        then: sinon.spy()
      };
      memSvc = {
        getJvmMemory: sinon.stub().returns(promise)
      };
      scaleSvc = {
        format: sinon.stub().returns({
          scale: 1024 * 1024,
          unit: 'MiB'
        })
      };

      sanitizeSvc = {
        sanitize: sinon.stub().returns('sanitized-mock')
      };

      translate = sinon.stub().returns({
        then: sinon.stub().yields('translate-mock')
      });

      dateFilter = sinon.stub().returns('mock-date-filter');
      dateFormat = { time: { medium: 'date-format-medium' } };

      ctrl = $controller('JvmMemoryController', {
        $stateParams: { jvmId: 'foo-jvmId' },
        $interval: interval,
        jvmMemoryService: memSvc,
        scaleBytesService: scaleSvc,
        sanitizeService: sanitizeSvc,
        $translate: translate,
        dateFilter: dateFilter,
        DATE_FORMAT: dateFormat
      });
      ctrl.$onInit();

      sinon.spy(ctrl, '_update');
      sinon.spy(ctrl, '_stop');
    });
  });

  afterEach(() => {
    ctrl._update.restore();
    ctrl._stop.restore();
  });

  it('should exist', () => {
    should.exist(ctrl);
  });

  it('should assign an initial metaspaceSnapshotData object', () => {
    ctrl.should.have.ownProperty('metaspaceSnapshotData');
    ctrl.metaspaceSnapshotData.should.deepEqual({
      used: 0,
      total: 0
    });
  });

  it('should assign a metaspaceBarConfig object', () => {
    ctrl.should.have.ownProperty('metaspaceBarConfig');
    ctrl.metaspaceBarConfig.should.deepEqual({
      units: 'B'
    });
  });

  describe('metaspaceLineConfig', () => {
    it('should be assigned after init', () => {
      ctrl.should.have.ownProperty('metaspaceLineConfig');
    });

    it('should provide an x-axis tick formatter', () => {
      let fn = ctrl.metaspaceLineConfig.axis.x.tick.format;
      dateFilter.should.not.be.called();
      fn(1234);
      dateFilter.should.be.calledOnce();
      dateFilter.should.be.calledWith(1234, dateFormat.time.medium);
    });
  });

  describe('getLineConfig', () => {
    it('should return a chart configuration', () => {
      let genIndex = 2;
      let spaceIndex = 4;
      let cfg = ctrl.getLineConfig(genIndex, spaceIndex);
      cfg.chartId.should.equal('line-gen-2-space-4');
    });

    it('should provide an x-axis tick formatter', () => {
      let genIndex = 2;
      let spaceIndex = 4;
      let cfg = ctrl.getLineConfig(genIndex, spaceIndex);
      let fn = cfg.axis.x.tick.format;
      dateFilter.should.not.be.called();
      fn(1234);
      dateFilter.should.be.calledOnce();
      dateFilter.should.be.calledWith(1234, dateFormat.time.medium);
    });

    it('should cache configs', () => {
      let spy = sinon.spy(ctrl.generationLineConfigs, 'set');
      let genIndex = 2;
      let spaceIndex = 4;
      ctrl.getLineConfig(genIndex, spaceIndex);
      spy.should.be.calledOnce();
      ctrl.getLineConfig(genIndex, spaceIndex);
      spy.should.be.calledOnce();
      spy.restore();
    });
  });

  it('should update on init', () => {
    memSvc.getJvmMemory.should.be.calledWith('foo-jvmId');
  });

  it('should initialize refreshRate to \'2000\'', () => {
    ctrl.refreshRate.should.equal('2000');
  });

  it('should reset interval on refreshRate change', () => {
    ctrl.should.have.ownProperty('_refresh');
    ctrl.refreshRate = 1;
    interval.should.be.calledWith(sinon.match.func, sinon.match(1));
    ctrl.should.have.ownProperty('_refresh');
    ctrl._refresh.should.equal('interval-sentinel');
  });

  it('should disable when set refreshRate is called with a non-positive value', () => {
    ctrl._stop.should.not.be.called();
    ctrl._update.should.not.be.called();

    ctrl.refreshRate = -1;

    ctrl._stop.should.be.calledOnce();
    ctrl._update.should.not.be.called();
    ctrl.should.not.have.ownProperty('_refresh');
  });

  it('should call controller#_update() on refresh', () => {
    ctrl.refreshRate = 1;
    let func = interval.args[0][0];
    let callCount = ctrl._update.callCount;
    func();
    ctrl._update.callCount.should.equal(callCount + 1);
  });

  describe('ondestroy handler', () => {
    it('should cancel refresh', () => {
      ctrl.$onDestroy();
      interval.cancel.should.be.calledWith('interval-sentinel');
    });

    it('should do nothing if _refresh is undefined', () => {
      delete ctrl._refresh;
      ctrl.$onDestroy();
      interval.cancel.should.not.be.called();
    });
  });

  describe('_update', () => {
    let data, func;
    beforeEach(() => {
      func = promise.then.args[0][0];
      data = [{
        agentId: 'foo-agentId',
        jvmId: 'foo-jvmId',
        timeStamp: Date.now(),
        metaspaceMaxCapacity: { $numberLong: '0' },
        metaspaceMinCapacity: { $numberLong: '0' },
        metaspaceCapacity: { $numberLong: (40 * 1024 * 1024).toString() },
        metaspaceUsed: { $numberLong: (20 * 1024 * 1024).toString() },
        generations: [
          {
            capacity: { $numberLong: (10 * 1024 * 1024).toString() },
            collector: 'Shenandoah',
            maxCapacity: { $numberLong: (60 * 1024 * 1024).toString() },
            name: 'Generation 0',
            spaces: [
              {
                capacity: { $numberLong: (50 * 1024 * 1024).toString() },
                index: 0,
                maxCapacity: { $numberLong: (80 * 1024 * 1024).toString() },
                name: 'Gen 0 Space 0',
                used: { $numberLong: (30 * 1024 * 1024).toString() }
              }
            ]
          }
        ]
      }];
      func(data);
    });

    it('should update metaspaceSnapshotData', () => {
      ctrl.metaspaceSnapshotData.should.deepEqual({
        used: 20,
        total: 40
      });
      scaleSvc.format.should.be.calledTwice();
      scaleSvc.format.firstCall.should.be.calledWithMatch(20 * 1024 * 1024);
    });

    it('should add generationSnapshotData', () => {
      ctrl.generationSnapshotData.should.deepEqual({
        0: {
          index: 0,
          name: 'Generation 0',
          collector: 'Shenandoah',
          spaces: [
            {
              index: 0,
              used: 30,
              total: 50
            }
          ]
        }
      });
      scaleSvc.format.should.be.calledTwice();
      scaleSvc.format.secondCall.should.be.calledWithMatch(30 * 1024 * 1024);
    });

    it('should update generationSnapshotData on repeated calls', () => {
      let generation = data[0].generations[0];
      let space = generation.spaces[0];
      space.capacity = { $numberLong: (100 * 1024 * 1024).toString() };
      space.used = { $numberLong: (50 * 1024 * 1024).toString() };
      func(data);
      ctrl.generationSnapshotData.should.deepEqual({
        0: {
          index: 0,
          name: 'Generation 0',
          collector: 'Shenandoah',
          spaces: [
            {
              index: 0,
              used: 50,
              total: 100
            }
          ]
        }
      });
      scaleSvc.format.callCount.should.equal(4);
      scaleSvc.format.args[0][0].should.deepEqual(20 * 1024 * 1024);
      scaleSvc.format.args[1][0].should.deepEqual(30 * 1024 * 1024);
      scaleSvc.format.args[2][0].should.deepEqual(20 * 1024 * 1024);
      scaleSvc.format.args[3][0].should.deepEqual(50 * 1024 * 1024);
    });
  });

  describe('_trimData', () => {
    it('should trim old metaspace data', () => {
      let now = Date.now();
      ctrl._metaspaceData = {
        timestamps: [now - 120 * 60 * 1000, now],
        used: [100, 200],
        capacity: [200, 400]
      };
      ctrl._trimData();
      ctrl._metaspaceData.should.deepEqual({
        timestamps: [now],
        used: [200],
        capacity: [400]
      });
    });

    it('should trim old generational data', () => {
      let now = Date.now();
      ctrl._generationData.set('foo-key', {
        timestamps: [now - 120 * 60 * 1000, now],
        used: [100, 200],
        capacity: [200, 400]
      });
      ctrl._trimData();
      ctrl._generationData.get('foo-key').should.deepEqual({
        timestamps: [now],
        used: [200],
        capacity: [400]
      });
    });
  });

  describe('dataAgeLimit', () => {
    it('should cause a data trim on change', () => {
      sinon.spy(ctrl, '_trimData');
      ctrl._trimData.should.not.be.called;
      ctrl.dataAgeLimit = 10000;
      ctrl._trimData.should.be.calledOnce();
      ctrl._trimData.restore();
    });

    it('should reflect changes in getter', () => {
      ctrl.dataAgeLimit.should.equal('600000');
      ctrl.dataAgeLimit = 10000;
      ctrl.dataAgeLimit.should.equal('10000');
    });
  });

  describe('multichartMetaspace', () => {
    it('should return a promise', () => {
      let res = ctrl.multichartMetaspace();
      res.should.be.a.Promise();
    });

    it('should resolve jvm-memory stat', done => {
      promise.then.should.be.calledOnce();
      let res = ctrl.multichartMetaspace();
      res.then(v => {
        v.should.equal(9001);
        done();
      });
      promise.then.should.be.calledTwice();
      let prom = promise.then.secondCall.args[0];
      prom([{
        metaspaceUsed: { $numberLong: '9001' }
      }]);
    });
  });

  describe('multichartSpace', () => {
    it('should return a promise', () => {
      let res = ctrl.multichartSpace(0, 0);
      res.should.be.a.Promise();
    });

    it('should resolve space used stat', done => {
      promise.then.should.be.calledOnce();
      let res = ctrl.multichartSpace(1, 1);
      res.then(v => {
        v.should.equal(400);
        done();
      });
      promise.then.should.be.calledTwice();
      let prom = promise.then.secondCall.args[0];
      prom([{
        generations: [
          {
            spaces: [
              {
                used: 100,
                total: 150
              },
              {
                used: 200,
                total: 250
              }
            ]
          },
          {
            spaces: [
              {
                used: 300,
                total: 350
              },
              {
                used: 400,
                total: 450
              }
            ]
          }
        ]
      }]);
    });
  });

  it('should delegate sanitization to sanitizeService', () => {
    sanitizeSvc.sanitize.should.not.be.called();
    ctrl.sanitize('foo').should.equal('sanitized-mock');
    sanitizeSvc.sanitize.should.be.calledOnce();
    sanitizeSvc.sanitize.should.be.calledWith('foo');
  });

});