changeset 230:eebc90a53b7f

jvm-io loads historical data Reviewed-by: jkang Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2017-September/025222.html
author Andrew Azores <aazores@redhat.com>
date Thu, 28 Sep 2017 14:20:44 -0400
parents 1887aaa6d7a5
children ba401daa2732
files mock-api/endpoints/jvm-io.endpoint.js src/app/components/jvm-info/jvm-io/jvm-io.controller.js src/app/components/jvm-info/jvm-io/jvm-io.controller.spec.js src/app/components/jvm-info/jvm-io/jvm-io.html src/app/components/jvm-info/jvm-io/jvm-io.service.js src/app/components/jvm-info/jvm-io/jvm-io.service.spec.js
diffstat 6 files changed, 215 insertions(+), 93 deletions(-) [+]
line wrap: on
line diff
--- a/mock-api/endpoints/jvm-io.endpoint.js	Thu Sep 28 10:07:13 2017 -0400
+++ b/mock-api/endpoints/jvm-io.endpoint.js	Thu Sep 28 14:20:44 2017 -0400
@@ -5,19 +5,33 @@
   server.app.get('/jvm-io/0.0.1/jvms/:jvmId', function (req, res) {
     server.logRequest('jvm-io', req);
 
+    var limit = req.query.limit;
     var jvmId = req.params.jvmId;
 
-    var response = [
-      {
+    var count;
+    if (limit == 0) {
+      count = 60;
+    } else if (limit == 1) {
+      count = 0;
+    } else {
+      count = 0;
+    }
+
+    var response = [];
+    for (var i = count; i >= 0; i--) {
+      let date = Date.now() - (i * 10000);
+      let data = {
         agentId: 'foo-agentId',
         jvmId: jvmId,
-        timeStamp: { $numberLong: Date.now().toString() },
-        charactersRead: { $numberLong: _.floor((Math.random() * 10000000)).toString() },
-        charactersWritten: { $numberLong: _.floor((Math.random() * 10000000)).toString() },
-        readSysCalls: { $numberLong: _.floor((Math.random() * 25000)).toString() },
-        writeSysCalls: { $numberLong: _.floor((Math.random() * 120000)).toString() }
-      }
-    ];
+        timeStamp: { $numberLong: date.toString() },
+        charactersRead: { $numberLong: _.floor(date / 10000000).toString() },
+        charactersWritten: { $numberLong: _.floor((date / 12000000)).toString() },
+        readSysCalls: { $numberLong: _.floor(date / 20000000).toString() },
+        writeSysCalls: { $numberLong: _.floor(date / 30000000).toString() }
+      };
+      response.push(data);
+    }
+    console.log(response);
 
     res.setHeader('Content-Type', 'application/json');
     res.send(JSON.stringify(
--- a/src/app/components/jvm-info/jvm-io/jvm-io.controller.js	Thu Sep 28 10:07:13 2017 -0400
+++ b/src/app/components/jvm-info/jvm-io/jvm-io.controller.js	Thu Sep 28 14:20:44 2017 -0400
@@ -40,9 +40,15 @@
     this._dateFormat = DATE_FORMAT;
     this._metricToNumber = metricToNumberFilter;
 
-    this._refreshRate = 1000;
-    this._dataAgeLimit = 30000;
-    this._makeChartConfig().then(() => this._start());
+    this._refreshRate = 10000;
+    this._dataAgeLimit = 600000;
+
+    this._makeChartConfig();
+  }
+
+  $onInit() {
+    this._loadHistoricalData();
+    this._start();
   }
 
   $onDestroy () {
@@ -50,7 +56,7 @@
   }
 
   _makeChartConfig () {
-    return this._translate([
+    this._translate([
       'jvmIo.chart.X_LABEL',
       'jvmIo.chart.Y1_LABEL',
       'jvmIo.chart.Y2_LABEL',
@@ -133,8 +139,9 @@
   }
 
   set dataAgeLimit (val) {
+    this._clearData();
     this._dataAgeLimit = val;
-    this._trimData();
+    this._loadHistoricalData();
   }
 
   get dataAgeLimit () {
@@ -144,7 +151,6 @@
   _start () {
     this._stop();
     this._refresh = this._interval(() => this._update(), this._refreshRate);
-    this._update();
   }
 
   _stop () {
@@ -154,6 +160,11 @@
     }
   }
 
+  _clearData () {
+    let firstRow = this.config.data.rows[0];
+    this.config.data.rows = [firstRow];
+  }
+
   _trimData () {
     let now = Date.now();
     let limit = now - this._dataAgeLimit;
@@ -162,22 +173,27 @@
     }
   }
 
+  _loadHistoricalData () {
+    this._svc.getHistoricalData(this.jvmId, Date.now() - this._dataAgeLimit).then(updates =>
+      updates.forEach(update => this._processUpdateRow(update)));
+  }
+
   _update () {
-    if (!angular.isDefined(this.jvmId)) {
-      return;
-    }
-    this._svc.getJvmIoData(this.jvmId).then(res => {
-      let update = res.data.response[0];
-      this.config.data.rows.push([
-        this._metricToNumber(update.timeStamp),
-        this._metricToNumber(update.charactersRead),
-        this._metricToNumber(update.charactersWritten),
-        this._metricToNumber(update.readSysCalls),
-        this._metricToNumber(update.writeSysCalls),
-      ]);
+    this._svc.getJvmIoData(this.jvmId).then(update => {
+      this._processUpdateRow(update);
       this._trimData();
     });
   }
+
+  _processUpdateRow (update) {
+    this.config.data.rows.push([
+      this._metricToNumber(update.timeStamp),
+      this._metricToNumber(update.charactersRead),
+      this._metricToNumber(update.charactersWritten),
+      this._metricToNumber(update.readSysCalls),
+      this._metricToNumber(update.writeSysCalls),
+    ]);
+  }
 }
 
 export default angular
--- a/src/app/components/jvm-info/jvm-io/jvm-io.controller.spec.js	Thu Sep 28 10:07:13 2017 -0400
+++ b/src/app/components/jvm-info/jvm-io/jvm-io.controller.spec.js	Thu Sep 28 14:20:44 2017 -0400
@@ -40,6 +40,9 @@
         getJvmIoData: sinon.stub().returns({
           then: svcPromise
         }),
+        getHistoricalData: sinon.stub().returns({
+          then: svcPromise
+        }),
         promise: svcPromise
       };
       interval = sinon.stub().returns('intervalMock');
@@ -83,13 +86,13 @@
     should.exist(ctrl);
   });
 
-  describe('init', () => {
-    it('should set refresh rate to 1 second', () => {
-      ctrl.refreshRate.should.equal('1000');
+  describe('properties', () => {
+    it('should set refresh rate to 10 seconds', () => {
+      ctrl.refreshRate.should.equal('10000');
     });
 
-    it('should set data age limit to 30 seconds', () => {
-      ctrl.dataAgeLimit.should.equal('30000');
+    it('should set data age limit to 10 minutes', () => {
+      ctrl.dataAgeLimit.should.equal('600000');
     });
 
     it('should use translations', () => {
@@ -107,6 +110,20 @@
     });
   });
 
+  describe('$onInit ()', () => {
+    beforeEach(() => {
+      ctrl.$onInit();
+    });
+
+    it('should load historical data', () => {
+      svc.getHistoricalData.should.be.calledOnce();
+    });
+
+    it('should start periodic live updates', () => {
+      interval.should.be.calledOnce();
+    });
+  });
+
   describe('chart config', () => {
     it('should format x-axis ticks', () => {
       let fn = ctrl.config.axis.x.tick.format;
@@ -137,50 +154,57 @@
 
   describe('$onDestroy', () => {
     it('should do nothing if controller is not started', () => {
-      ctrl._stop();
-      interval.cancel.should.be.calledOnce();
+      interval.cancel.should.not.be.called();
       ctrl.$onDestroy();
-      interval.cancel.should.be.calledOnce();
+      interval.cancel.should.not.be.called();
     });
 
     it('should stop the controller if already started', () => {
       ctrl._start();
-      interval.cancel.should.be.calledOnce();
+      interval.cancel.should.not.be.called();
       ctrl.$onDestroy();
-      interval.cancel.should.be.calledTwice();
+      interval.cancel.should.be.calledOnce();
     });
   });
 
   describe('refreshRate', () => {
     it('should set interval and disable if <= 0', () => {
+      interval.should.not.be.called();
+      ctrl.refreshRate = 10000;
+      interval.cancel.should.not.be.called();
       interval.should.be.calledOnce();
-      ctrl.refreshRate = 10000;
+      interval.should.be.calledWith(sinon.match.func, 10000);
+      ctrl.refreshRate = -1;
       interval.cancel.should.be.calledOnce();
-      interval.should.be.calledTwice();
-      interval.secondCall.should.be.calledWith(sinon.match.func, 10000);
-      ctrl.refreshRate = -1;
-      interval.cancel.should.be.calledTwice();
-      interval.should.be.calledTwice();
+      interval.should.be.calledOnce();
     });
 
     it('should reflect changes in getter', () => {
-      ctrl.refreshRate.should.equal('1000');
+      ctrl.refreshRate.should.equal('10000');
       ctrl.refreshRate = 2000;
       ctrl.refreshRate.should.equal('2000');
     });
   });
 
   describe('dataAgeLimit', () => {
-    it('should cause a data trim on change', () => {
-      sinon.spy(ctrl, '_trimData');
-      ctrl._trimData.should.not.be.called;
+    it('should cause a data clear on change', () => {
+      sinon.spy(ctrl, '_clearData');
+      ctrl._clearData.should.not.be.called;
       ctrl.dataAgeLimit = 10000;
-      ctrl._trimData.should.be.calledOnce();
-      ctrl._trimData.restore();
+      ctrl._clearData.should.be.calledOnce();
+      ctrl._clearData.restore();
+    });
+
+    it('should cause a historical data load on change', () => {
+      sinon.spy(ctrl, '_loadHistoricalData');
+      ctrl._loadHistoricalData.should.not.be.called;
+      ctrl.dataAgeLimit = 10000;
+      ctrl._loadHistoricalData.should.be.calledOnce();
+      ctrl._loadHistoricalData.restore();
     });
 
     it('should reflect changes in getter', () => {
-      ctrl.dataAgeLimit.should.equal('30000');
+      ctrl.dataAgeLimit.should.equal('600000');
       ctrl.dataAgeLimit = 10000;
       ctrl.dataAgeLimit.should.equal('10000');
     });
@@ -188,15 +212,15 @@
 
   describe('_start', () => {
     it('should perform updates on an interval', () => {
-      interval.should.be.calledOnce();
+      interval.should.not.be.called();
       ctrl._start();
-      interval.should.be.calledTwice();
-      interval.secondCall.should.be.calledWith(sinon.match.func, 1000);
+      interval.should.be.calledOnce();
+      interval.should.be.calledWith(sinon.match.func, 10000);
 
-      let func = interval.args[1][0];
+      let func = interval.args[0][0];
+      svc.getJvmIoData.should.not.be.called();
+      func();
       svc.getJvmIoData.should.be.calledOnce();
-      func();
-      svc.getJvmIoData.should.be.calledTwice();
     });
   });
 
@@ -204,37 +228,57 @@
     it('should do nothing if not started', () => {
       interval.cancel.should.not.be.called();
       ctrl._stop();
-      interval.cancel.should.be.calledOnce();
-      interval.cancel.should.be.calledWith('intervalMock');
-      ctrl._stop();
-      interval.cancel.should.be.calledOnce();
+      interval.cancel.should.not.be.called();
+    });
+  });
+
+  describe('_loadHistoricaldata', () => {
+    it('should add update data to chart data', () => {
+      ctrl._loadHistoricalData();
+      svc.promise.should.be.calledOnce();
+      svc.promise.should.be.calledWith(sinon.match.func);
+      let stamp = Date.now();
+      svc.promise.args[0][0](
+        [
+          {
+            timeStamp: { $numberLong: (stamp - 10000).toString() },
+            charactersRead: { $numberLong: '1000000' },
+            charactersWritten: { $numberLong: '500000' },
+            readSysCalls: { $numberLong: '100' },
+            writeSysCalls: { $numberLong: '50' }
+          },
+          {
+            timeStamp: { $numberLong: stamp.toString() },
+            charactersRead: { $numberLong: '1000001' },
+            charactersWritten: { $numberLong: '500001' },
+            readSysCalls: { $numberLong: '101' },
+            writeSysCalls: { $numberLong: '51' }
+          }
+        ]
+      );
+      ctrl.config.data.rows.should.deepEqual([
+        ['date', 'characters read', 'characters written', 'read sys calls', 'write sys calls'],
+        [(stamp - 10000), 1000000, 500000, 100, 50],
+        [stamp, 1000001, 500001, 101, 51]
+      ]);
     });
   });
 
   describe('_update', () => {
-    it('should do nothing if jvmId not yet bound', () => {
-      svc.getJvmIoData.should.not.be.called();
-      delete ctrl.jvmId;
-      ctrl._update();
-      svc.getJvmIoData.should.not.be.called();
-    });
-
     it('should add update data to chart data', () => {
       ctrl._update();
       svc.promise.should.be.calledOnce();
       svc.promise.should.be.calledWith(sinon.match.func);
       let stamp = Date.now();
-      svc.promise.args[0][0]({
-        data: {
-          response: [{
-            timeStamp: { $numberLong: stamp.toString() },
-            charactersRead: { $numberLong: '1000000' },
-            charactersWritten: { $numberLong: '500000' },
-            readSysCalls: { $numberLong: '100' },
-            writeSysCalls: { $numberLong: '50' }
-          }]
+      svc.promise.args[0][0](
+        {
+          timeStamp: { $numberLong: stamp.toString() },
+          charactersRead: { $numberLong: '1000000' },
+          charactersWritten: { $numberLong: '500000' },
+          readSysCalls: { $numberLong: '100' },
+          writeSysCalls: { $numberLong: '50' }
         }
-      });
+      );
       ctrl.config.data.rows.should.deepEqual([
         ['date', 'characters read', 'characters written', 'read sys calls', 'write sys calls'],
         [stamp, 1000000, 500000, 100, 50]
@@ -245,7 +289,7 @@
   describe('_trimData', () => {
     it('should remove datasets older than dataAgeLimit', () => {
       let now = Date.now();
-      let expired = now - 60000;
+      let expired = now - ctrl._dataAgeLimit - 1;
       ctrl.config.data.rows.push([
         expired, 1, 1, 1, 1
       ]);
--- a/src/app/components/jvm-info/jvm-io/jvm-io.html	Thu Sep 28 10:07:13 2017 -0400
+++ b/src/app/components/jvm-info/jvm-io/jvm-io.html	Thu Sep 28 14:20:44 2017 -0400
@@ -5,22 +5,22 @@
       <label for="refreshCombo" class="label label-info" translate>jvmIo.REFRESH_RATE_LABEL</label>
       <select name="refreshCombo" class="combobox form-control" ng-model="$ctrl.refreshRate">
         <option value="-1" translate>jvmIo.refresh.DISABLED</option>
-        <option value="1000" selected translate="jvmIo.refresh.SECONDS" translate-values="{ SECONDS: 1, DEFAULT: true }" translate-interpolation="messageformat"></option>
-        <option value="2000" translate="jvmIo.refresh.SECONDS" translate-values="{ SECONDS: 2 }" translate-interpolation="messageformat"></option>
         <option value="5000" translate="jvmIo.refresh.SECONDS" translate-values="{ SECONDS: 5 }" translate-interpolation="messageformat"></option>
-        <option value="10000" translate="jvmIo.refresh.SECONDS" translate-values="{ SECONDS: 10 }" translate-interpolation="messageformat"></option>
+        <option value="10000" selected translate="jvmIo.refresh.SECONDS" translate-values="{ SECONDS: 10, DEFAULT: true }" translate-interpolation="messageformat"></option>
         <option value="30000" translate="jvmIo.refresh.SECONDS" translate-values="{ SECONDS: 30 }" translate-interpolation="messageformat"></option>
+        <option value="60000" translate="jvmIo.refresh.SECONDS" translate-values="{ SECONDS: 60 }" translate-interpolation="messageformat"></option>
       </select>
     </div>
 
     <div class="col-xs-12 col-md-3">
       <label for="dataAgeCombo" class="label label-info" translate>jvmIo.MAX_DATA_AGE_LABEL</label>
       <select name="dataAgeCombo" class="combobox form-control" ng-model="$ctrl.dataAgeLimit">
-        <option value="10000" translate="jvmIo.dataAge.SECONDS" translate-values="{ SECONDS: 10 }" translate-interpolation="messageformat"></option>
-        <option value="30000" selected translate="jvmIo.dataAge.SECONDS" translate-values="{ SECONDS: 30, DEFAULT: true }" translate-interpolation="messageformat"></option>
+        <option value="30000" translate="jvmIo.dataAge.SECONDS" translate-values="{ SECONDS: 30 }" translate-interpolation="messageformat"></option>
         <option value="60000" translate="jvmIo.dataAge.MINUTES" translate-values="{ MINUTES: 1 }" translate-interpolation="messageformat"></option>
         <option value="300000" translate="jvmIo.dataAge.MINUTES" translate-values="{ MINUTES: 5 }" translate-interpolation="messageformat"></option>
+        <option value="600000" selected translate="jvmIo.dataAge.MINUTES" translate-values="{ MINUTES: 10, DEFAULT: true }" translate-interpolation="messageformat"></option>
         <option value="900000" translate="jvmIo.dataAge.MINUTES" translate-values="{ MINUTES: 15 }" translate-interpolation="messageformat"></option>
+        <option value="1800000" translate="jvmIo.dataAge.MINUTES" translate-values="{ MINUTES: 30 }" translate-interpolation="messageformat"></option>
       </select>
     </div>
   </div><!-- row -->
--- a/src/app/components/jvm-info/jvm-io/jvm-io.service.js	Thu Sep 28 10:07:13 2017 -0400
+++ b/src/app/components/jvm-info/jvm-io/jvm-io.service.js	Thu Sep 28 14:20:44 2017 -0400
@@ -35,9 +35,23 @@
     this._gatewayUrl = gatewayUrl;
   }
 
+  getHistoricalData (jvmId, since) {
+    let params = {
+      sort: '-timeStamp',
+      limit: 0,
+      query: `timeStamp>=${since}`
+    };
+    return this._http.get(urlJoin(this._gatewayUrl, 'jvm-io', '0.0.1', 'jvms', jvmId), { params: params })
+      .then(res => res.data.response);
+  }
+
   getJvmIoData (jvmId) {
-    let params = { sort: '-timeStamp' };
-    return this._http.get(urlJoin(this._gatewayUrl, 'jvm-io', '0.0.1', 'jvms', jvmId), { params: params });
+    let params = {
+      sort: '-timeStamp',
+      limit: 1
+    };
+    return this._http.get(urlJoin(this._gatewayUrl, 'jvm-io', '0.0.1', 'jvms', jvmId), { params: params })
+      .then(res => res.data.response[0]);
   }
 }
 
--- a/src/app/components/jvm-info/jvm-io/jvm-io.service.spec.js	Thu Sep 28 10:07:13 2017 -0400
+++ b/src/app/components/jvm-info/jvm-io/jvm-io.service.spec.js	Thu Sep 28 14:20:44 2017 -0400
@@ -57,19 +57,53 @@
   describe('getJvmIodata (jvmId)', () => {
     it('should resolve mock data', done => {
       let expected = {
-        timeStamp: { $numberLong: Date.now().toString() },
-        charactersRead: { $numberLong: '100000' },
-        charactersWritten: { $numberLong: '50000' },
-        readSysCalls: { $numberLong: '100' },
-        writeSysCalls: { $numberLong: '50' }
+        response: [{
+          timeStamp: { $numberLong: Date.now().toString() },
+          charactersRead: { $numberLong: '100000' },
+          charactersWritten: { $numberLong: '50000' },
+          readSysCalls: { $numberLong: '100' },
+          writeSysCalls: { $numberLong: '50' }
+        }]
       };
-      httpBackend.when('GET', 'http://example.com:1234/jvm-io/0.0.1/jvms/foo-jvmId?sort=-timeStamp')
+      httpBackend.when('GET', 'http://example.com:1234/jvm-io/0.0.1/jvms/foo-jvmId?limit=1&sort=-timeStamp')
         .respond(expected);
       svc.getJvmIoData('foo-jvmId').then(res => {
-        res.data.should.deepEqual(expected);
+        res.should.deepEqual(expected.response[0]);
         done();
       });
-      httpBackend.expectGET('http://example.com:1234/jvm-io/0.0.1/jvms/foo-jvmId?sort=-timeStamp');
+      httpBackend.expectGET('http://example.com:1234/jvm-io/0.0.1/jvms/foo-jvmId?limit=1&sort=-timeStamp');
+      httpBackend.flush();
+      scope.$apply();
+    });
+  });
+
+  describe('getHistoricalData (jvmId, since)', () => {
+    it('should resolve mock data', done => {
+      let expected = {
+        response: [
+          {
+            timeStamp: { $numberLong: (Date.now() - 10000).toString() },
+            charactersRead: { $numberLong: '100000' },
+            charactersWritten: { $numberLong: '50000' },
+            readSysCalls: { $numberLong: '100' },
+            writeSysCalls: { $numberLong: '50' }
+          },
+          {
+            timeStamp: { $numberLong: Date.now().toString() },
+            charactersRead: { $numberLong: '200000' },
+            charactersWritten: { $numberLong: '60000' },
+            readSysCalls: { $numberLong: '200' },
+            writeSysCalls: { $numberLong: '60' }
+          },
+        ]
+      };
+      httpBackend.when('GET', 'http://example.com:1234/jvm-io/0.0.1/jvms/foo-jvmId?limit=0&query=timeStamp%3E%3D150000&sort=-timeStamp')
+        .respond(expected);
+      svc.getHistoricalData('foo-jvmId', 150000).then(res => {
+        res.should.deepEqual(expected.response);
+        done();
+      });
+      httpBackend.expectGET('http://example.com:1234/jvm-io/0.0.1/jvms/foo-jvmId?limit=0&query=timeStamp%3E%3D150000&sort=-timeStamp');
       httpBackend.flush();
       scope.$apply();
     });