changeset 263:87a683af97e8

Use jvm-gc /delta Reviewed-by: jkang Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2017-October/025439.html
author Andrew Azores <aazores@redhat.com>
date Tue, 24 Oct 2017 15:51:36 -0400
parents 9f3ee8157ac6
children 71f13dfad691
files mock-api/endpoints/jvm-gc.endpoint.js src/app/components/jvm-info/jvm-gc/jvm-gc.controller.js src/app/components/jvm-info/jvm-gc/jvm-gc.controller.spec.js src/app/components/jvm-info/jvm-gc/jvm-gc.service.js src/app/components/jvm-info/jvm-gc/jvm-gc.service.spec.js
diffstat 5 files changed, 164 insertions(+), 272 deletions(-) [+]
line wrap: on
line diff
--- a/mock-api/endpoints/jvm-gc.endpoint.js	Tue Oct 24 15:50:49 2017 -0400
+++ b/mock-api/endpoints/jvm-gc.endpoint.js	Tue Oct 24 15:51:36 2017 -0400
@@ -1,39 +1,47 @@
 function jvmGc (server) {
   var _ = require('lodash');
   server.init('jvmGc');
-  var accumulatedMicros = 1000;
+
   server.app.get('/jvm-gc/0.0.3/jvms/:jvmId', function (req, res) {
     server.logRequest('jvm-gc', req);
 
     var jvmId = req.params.jvmId;
 
-    var limit = req.query.limit || 1;
+    var limit = req.query.limit;
+    if (limit == 1) {
+      var event = Math.random() > 0.8;
+      var micros = event ? Math.random() * 1000 : 0;
+      res.setHeader('Content-Type', 'application/json');
+      res.send(JSON.stringify({
+        response: [{
+          jvmId: jvmId,
+          collectorName: 'foo-collector',
+          timeStamp: { $numberLong: Date.now().toString() },
+          wallTimeInMicros: { $numberLong: micros }
+        }]
+      }));
+    }
+  });
 
-    var response = [];
-    for (var i = 0; i < limit; i++) {
-      if (Math.random() > 0.9) {
-        accumulatedMicros = _.floor(accumulatedMicros * 1.1);
-      }
-      if (accumulatedMicros > 1000000) {
-        // clients probably won't like this "rollover", but we don't want to
-        // grow unbounded either
-        accumulatedMicros = 1000;
-      }
-      var data = {
-        agentId: 'foo-agentId',
-        jvmId: jvmId,
-        timeStamp: { $numberLong: (Date.now() - i).toString() },
-        collectorName: 'fooCollector',
-        runCount: { $numberLong: '0' },
-        wallTimeInMicros: { $numberLong: accumulatedMicros.toString() }
-      };
-      response.push(data);
-    }
+  server.app.get('/jvm-gc/0.0.3/delta/:jvmId', function (req, res) {
+    server.logRequest('jvm-gc', req);
+
+    var jvmId = req.params.jvmId;
+
+    var event = Math.random() > 0.8;
+    var micros = event ? Math.random() * 1000 : 0;
+
+    var data = {
+      jvmId: jvmId,
+      collectorName: 'foo-collector',
+      timeStamp: { $numberLong: Date.now().toString() },
+      wallTimeDelta: { $numberLong: micros }
+    };
 
     res.setHeader('Content-Type', 'application/json');
     res.send(JSON.stringify(
       {
-        response: response
+        response: [data]
       }
     ));
   });
--- a/src/app/components/jvm-info/jvm-gc/jvm-gc.controller.js	Tue Oct 24 15:50:49 2017 -0400
+++ b/src/app/components/jvm-info/jvm-gc/jvm-gc.controller.js	Tue Oct 24 15:51:36 2017 -0400
@@ -145,21 +145,22 @@
   }
 
   _trimData () {
+    let now = Date.now();
+    let oldestLimit = now - parseInt(this.dataAgeLimit);
+
     for (let entry of this._collectorData) {
       let collector = entry[0];
       let samples = entry[1];
 
-      let now = Date.now();
-      let oldestLimit = now - parseInt(this.dataAgeLimit);
-
-      while (true) {
-        let oldest = samples[0];
-        if (angular.isDefined(oldest) && oldest.timestamp < oldestLimit) {
-          samples.shift();
-        } else {
+      let spliceCount = 0;
+      for (; spliceCount < samples.length; spliceCount++) {
+        let sample = samples[spliceCount];
+        if (sample.timestamp >= oldestLimit) {
           break;
         }
       }
+      samples.splice(0, spliceCount);
+      this._collectorData.set(collector, samples);
     }
   }
 
@@ -173,11 +174,10 @@
         yData: [collector]
       };
 
-      for (let i = 1; i < samples.length; i++) {
+      for (let i = 0; i < samples.length; i++) {
         let sample = samples[i];
-        let lastSample = samples[i - 1];
         data.xData.push(sample.timestamp);
-        data.yData.push(sample.micros - lastSample.micros);
+        data.yData.push(sample.micros);
       }
 
       this.chartData[collector] = data;
@@ -185,56 +185,27 @@
   }
 
   _update () {
-    this._svc.getJvmGcData(this.jvmId)
-      .then(resp => this._processData(resp), angular.noop);
+    this._svc.getJvmGcData(this.jvmId).then(resp => this._processData(resp));
   }
 
   _processData (resp) {
-    let seenCollectors = new Set();
-    let latestStamp = 0;
-    for (let i = resp.data.response.length - 1; i >= 0; i--) {
-      let data = resp.data.response[i];
-      let collectorName = data.collectorName;
-      let timestamp = this._metricToNumber(data.timeStamp);
-      let micros = this._metricToNumber(data.wallTimeInMicros);
+    resp.forEach(update => {
+      let timestamp = this._metricToNumber(update.timeStamp);
+      let collectorName = update.collectorName;
+      let micros = this._metricToNumber(update.wallTimeDelta);
 
-      seenCollectors.add(collectorName);
       this._makeConfig(collectorName, micros);
 
       if (!this._collectorData.has(collectorName)) {
         this._collectorData.set(collectorName, []);
       }
 
-      if (timestamp > latestStamp) {
-        latestStamp = timestamp;
-      }
-
       let collectorData = this._collectorData.get(collectorName);
-      if (collectorData.length === 0) {
-        collectorData.push({
-          timestamp: timestamp,
-          micros: micros
-        });
-      }
-      let last = collectorData[collectorData.length - 1];
-      if (timestamp > last.timestamp) {
-        collectorData.push({
-          timestamp: timestamp,
-          micros: micros
-        });
-      }
-    }
-
-    for (let entry of this._collectorData) {
-      let collectorName = entry[0];
-      let collectorData = entry[1];
-      if (!seenCollectors.has(collectorName)) {
-        collectorData.push({
-          timestamp: latestStamp,
-          micros: collectorData[collectorData.length - 1].micros
-        });
-      }
-    }
+      collectorData.push({
+        timestamp: timestamp,
+        micros: micros
+      });
+    });
 
     this._trimData();
 
@@ -247,8 +218,8 @@
 
   multichartFn (collector) {
     return new Promise(resolve => {
-      this._svc.getJvmGcData(this.jvmId, 1, collector).then(resp => {
-        resolve(this._metricToNumber(resp.data.response[0].wallTimeInMicros));
+      this._svc.getSnapshot(this.jvmId, collector).then(resp => {
+        resolve(this._metricToNumber(resp));
       });
     });
   }
--- a/src/app/components/jvm-info/jvm-gc/jvm-gc.controller.spec.js	Tue Oct 24 15:50:49 2017 -0400
+++ b/src/app/components/jvm-info/jvm-gc/jvm-gc.controller.spec.js	Tue Oct 24 15:51:36 2017 -0400
@@ -31,7 +31,7 @@
 
 describe('JvmGcController', () => {
 
-  let interval, dateFilterStub, dateFormatSpy, svc, promise, ctrl, translate, sanitizeService;
+  let interval, dateFilterStub, dateFormatSpy, svc, promise, snapshotPromise, ctrl, translate, sanitizeService;
   beforeEach(() => {
     angular.mock.module(filtersModule);
     angular.mock.module(servicesModule);
@@ -51,7 +51,11 @@
       interval.cancel = sinon.spy();
 
       promise = { then: sinon.spy() };
-      svc = { getJvmGcData: sinon.stub().returns(promise) };
+      snapshotPromise = { then: sinon.stub() };
+      svc = {
+        getJvmGcData: sinon.stub().returns(promise),
+        getSnapshot: sinon.stub().returns(snapshotPromise)
+      };
 
       sanitizeService = { sanitize: sinon.spy() };
 
@@ -70,7 +74,8 @@
         DATE_FORMAT: dateFormatSpy,
         jvmGcService: svc,
         sanitizeService: sanitizeService,
-        $translate: translate
+        $translate: translate,
+        metricToNumberFilter: m => parseInt(m.$numberLong)
       });
       ctrl.$onInit();
     });
@@ -91,25 +96,18 @@
     svc.getJvmGcData.should.be.calledTwice();
     promise.then.should.be.calledTwice();
 
-    promise.then.secondCall.should.be.calledWith(sinon.match.func, sinon.match.func);
+    promise.then.secondCall.should.be.calledWith(sinon.match.func);
     ctrl.collectors.should.deepEqual([]);
     let successHandler = promise.then.args[1][0];
-    successHandler({
-      data: {
-        response: [
-          {
-            collectorName: 'fooCollector',
-            timeStamp: { $numberLong: '100' },
-            wallTimeInMicros: { $numberLong: '50' }
-          }
-        ]
+    successHandler([
+      {
+        collectorName: 'fooCollector',
+        timeStamp: { $numberLong: '100' },
+        wallTimeInMicros: { $numberLong: '5050' },
+        wallTimeDelta: { $numberLong: '50' }
       }
-    });
+    ]);
     ctrl.collectors.should.deepEqual(['fooCollector']);
-
-    let errorHandler = promise.then.args[1][1];
-    errorHandler.should.equal(angular.noop);
-    errorHandler();
   });
 
   it('should reset interval on refreshRate change', () => {
@@ -260,156 +258,69 @@
 
       ctrl.chartData.should.deepEqual({
         fooCollector: {
-          xData: ['timestamps', 101],
-          yData: ['fooCollector', 10]
+          xData: ['timestamps', 100, 101],
+          yData: ['fooCollector', 50, 60]
         }
       });
     });
   });
 
   describe('_processData', () => {
-    it('should process singleton service results', () => {
-      ctrl.collectors.should.deepEqual([]);
-      ctrl.chartConfigs.should.deepEqual({});
-      ctrl._collectorData.has('fooCollector').should.be.false();
-      let timestamp = Date.now().toString();
-      ctrl._processData({
-        data: {
-          response: [
-            {
-              collectorName: 'fooCollector',
-              timeStamp: { $numberLong: timestamp },
-              wallTimeInMicros: { $numberLong: '50' }
-            }
-          ]
-        }
-      });
-      ctrl._collectorData.has('fooCollector').should.be.true();
-      ctrl._collectorData.get('fooCollector').should.be.an.Array();
-      ctrl._collectorData.get('fooCollector').length.should.equal(1);
-      ctrl._collectorData.get('fooCollector')[0].should.deepEqual({ timestamp: parseInt(timestamp), micros: 50 });
-    });
-
     it('should process multiple service results', () => {
       ctrl.collectors.should.deepEqual([]);
       ctrl.chartConfigs.should.deepEqual({});
       ctrl._collectorData.has('fooCollector').should.be.false();
-      let timestampA = Date.now().toString();
-      let timestampB = (Date.now() - 10).toString();
-      ctrl._processData({
-        data: {
-          response: [
-            {
-              collectorName: 'fooCollector',
-              timeStamp: { $numberLong: timestampA },
-              wallTimeInMicros: { $numberLong: '50' }
-            },
-            {
-              collectorName: 'fooCollector',
-              timeStamp: { $numberLong: timestampB },
-              wallTimeInMicros: { $numberLong: '25' }
-            }
-          ]
+      let timestampA = Date.now();
+      let timestampB = timestampA - 10;
+      ctrl._processData([
+        {
+          collectorName: 'fooCollector',
+          timeStamp: { $numberLong: timestampA.toString() },
+          wallTimeDelta: { $numberLong: '50' },
+          wallTimeInMicros: { $numberLong: '5050' }
+        },
+        {
+          collectorName: 'fooCollector',
+          timeStamp: { $numberLong: timestampB.toString() },
+          wallTimeDelta: { $numberLong: '25' },
+          wallTimeInMicros: { $numberLong: '2525' }
         }
-      });
+      ]);
       ctrl._collectorData.has('fooCollector').should.be.true();
-      ctrl._collectorData.get('fooCollector').should.be.an.Array();
-      ctrl._collectorData.get('fooCollector').length.should.equal(2);
-      ctrl._collectorData.get('fooCollector')[0].should.deepEqual({ timestamp: parseInt(timestampB), micros: 25 });
-      ctrl._collectorData.get('fooCollector')[1].should.deepEqual({ timestamp: parseInt(timestampA), micros: 50 });
+      let result = ctrl._collectorData.get('fooCollector');
+      result.should.be.an.Array();
+      result.should.deepEqual([
+        { timestamp: timestampA, micros: 50 },
+        { timestamp: timestampB, micros: 25 }
+      ]);
     });
 
     it('should append new data', () => {
-      let timestampA = Date.now().toString();
-      let timestampB = (Date.now() + 5000).toString();
-      ctrl._processData({
-        data: {
-          response: [
-            {
-              collectorName: 'fooCollector',
-              timeStamp: { $numberLong: timestampA },
-              wallTimeInMicros: { $numberLong: '50' }
-            }
-          ]
-        }
-      });
-      ctrl._processData({
-        data: {
-          response: [
-            {
-              collectorName: 'fooCollector',
-              timeStamp: { $numberLong: timestampB },
-              wallTimeInMicros: { $numberLong: '100' }
-            }
-          ]
-        }
-      });
-      ctrl._collectorData.has('fooCollector').should.be.true();
-      ctrl._collectorData.get('fooCollector').should.be.an.Array();
-      ctrl._collectorData.get('fooCollector').length.should.equal(2);
-      ctrl._collectorData.get('fooCollector')[0].should.deepEqual({ timestamp: parseInt(timestampA), micros: 50 });
-      ctrl._collectorData.get('fooCollector')[1].should.deepEqual({ timestamp: parseInt(timestampB), micros: 100 });
-    });
-
-    it('should append a sample with duplicate elapsed time if no sample received for a collector', () => {
-      let timestampA = Date.now().toString();
-      let timestampB = (Date.now() + 10).toString();
-      ctrl._processData({
-        data: {
-          response: [
-            {
-              collectorName: 'fooCollector',
-              timeStamp: { $numberLong: timestampA },
-              wallTimeInMicros: { $numberLong: '100' }
-            }
-          ]
+      let timestampA = Date.now();
+      let timestampB = timestampA + 5000;
+      ctrl._processData([
+        {
+          collectorName: 'fooCollector',
+          timeStamp: { $numberLong: timestampA.toString() },
+          wallTimeDelta: { $numberLong: '50' },
+          wallTimeInMicros: { $numberLong: '5050' }
         }
-      });
-      ctrl._processData({
-        data: {
-          response: [
-            {
-              collectorName: 'barCollector',
-              timeStamp: { $numberLong: timestampB },
-              wallTimeInMicros: { $numberLong: '200' }
-            }
-          ]
+      ]);
+      ctrl._processData([
+        {
+          collectorName: 'fooCollector',
+          timeStamp: { $numberLong: timestampB.toString() },
+          wallTimeDelta: { $numberLong: '100' },
+          wallTimeInMicros: { $numberLong: '10100' }
         }
-      });
-      ctrl._collectorData.get('fooCollector').length.should.equal(2);
-      ctrl._collectorData.get('fooCollector')[0].should.deepEqual({ timestamp: parseInt(timestampA), micros: 100 });
-      ctrl._collectorData.get('fooCollector')[1].should.deepEqual({ timestamp: parseInt(timestampB), micros: 100 });
-
-      ctrl._collectorData.get('barCollector').length.should.equal(1);
-      ctrl._collectorData.get('barCollector')[0].should.deepEqual({ timestamp: parseInt(timestampB), micros: 200 });
-    });
-
-    it('should ignore duplicate timestamps', () => {
-      let timestamp = Date.now().toString();
-      ctrl._processData({
-        data: {
-          response: [
-            {
-              collectorName: 'fooCollector',
-              timeStamp: { $numberLong: timestamp },
-              wallTimeInMicros: { $numberLong: 200 }
-            },
-            {
-              collectorName: 'fooCollector',
-              timeStamp: { $numberLong: timestamp },
-              wallTimeInMicros: { $numberLong: 100 }
-            },
-            {
-              collectorName: 'fooCollector',
-              timeStamp: { $numberLong: timestamp },
-              wallTimeInMicros: { $numberLong: 100 }
-            }
-          ]
-        }
-      });
-      ctrl._collectorData.get('fooCollector').length.should.equal(1);
-      // note: response is processed in reverse order, so 100 is seen first
-      ctrl._collectorData.get('fooCollector')[0].should.deepEqual({ timestamp: parseInt(timestamp), micros: 100 });
+      ]);
+      ctrl._collectorData.has('fooCollector').should.be.true();
+      let result = ctrl._collectorData.get('fooCollector');
+      result.should.be.an.Array();
+      result.should.deepEqual([
+        { timestamp: timestampA, micros: 50 },
+        { timestamp: timestampB, micros: 100 }
+      ]);
     });
   });
 
@@ -442,23 +353,16 @@
     });
 
     it('should resolve jvm-gc stat', done => {
-      promise.then.should.be.calledOnce();
-      let res = ctrl.multichartFn();
-      res.then(v => {
+      svc.getSnapshot.should.not.be.called();
+      snapshotPromise.then.should.not.be.called();
+      ctrl.multichartFn('foo-collector').then(v => {
         v.should.equal(400);
         done();
       });
-      promise.then.should.be.calledTwice();
-      let prom = promise.then.secondCall.args[0];
-      prom({
-        data: {
-          response: [
-            {
-              wallTimeInMicros: { $numberLong: '400' }
-            }
-          ]
-        }
-      });
+      svc.getSnapshot.should.be.calledOnce();
+      svc.getSnapshot.should.be.calledWith('foo-jvmId', 'foo-collector');
+      snapshotPromise.then.should.be.calledOnce();
+      snapshotPromise.then.yield({ $numberLong: '400' });
     });
   });
 
--- a/src/app/components/jvm-info/jvm-gc/jvm-gc.service.js	Tue Oct 24 15:50:49 2017 -0400
+++ b/src/app/components/jvm-info/jvm-gc/jvm-gc.service.js	Tue Oct 24 15:51:36 2017 -0400
@@ -35,15 +35,21 @@
     this.gatewayUrl = gatewayUrl;
   }
 
-  getJvmGcData (jvmId, limit = 1, collectorName) {
+  getJvmGcData (jvmId) {
+    return this.http.get(urlJoin(this.gatewayUrl, 'jvm-gc', '0.0.3', 'delta', jvmId)).then(resp => {
+      return resp.data.response;
+    });
+  }
+
+  getSnapshot (jvmId, collectorName) {
     let params = {
-      limit: limit,
+      limit: 1,
       sort: '-timeStamp',
+      query: 'collectorName==' + collectorName
     };
-    if (collectorName) {
-      params.query = 'collectorName==' + collectorName;
-    }
-    return this.http.get(urlJoin(this.gatewayUrl, 'jvm-gc', '0.0.3', 'jvms', jvmId), { params: params });
+    return this.http.get(urlJoin(this.gatewayUrl, 'jvm-gc', '0.0.3', 'jvms', jvmId), { params: params }).then(resp => {
+      return resp.data.response[0].wallTimeInMicros;
+    });
   }
 }
 
--- a/src/app/components/jvm-info/jvm-gc/jvm-gc.service.spec.js	Tue Oct 24 15:50:49 2017 -0400
+++ b/src/app/components/jvm-info/jvm-gc/jvm-gc.service.spec.js	Tue Oct 24 15:51:36 2017 -0400
@@ -54,48 +54,51 @@
     should.exist(svc);
   });
 
-  describe('getJvmGcdata(jvmId, limit, collectorName)', () => {
+  describe('getJvmGcdata (jvmId)', () => {
     it('should resolve mock data', done => {
       let expected = {
-        metaspace: 100
+        response: [
+          {
+            jvmId: 'foo-jvmId',
+            collectorName: 'foo-collector',
+            timeStamp: { $numberLong: '1234' },
+            wallTimeDelta: { $numberLong: '8888' },
+            wallTimeInMicros: { $numberLong: '9999' }
+          }
+        ]
       };
-      httpBackend.when('GET', 'http://example.com:1234/jvm-gc/0.0.3/jvms/foo-jvmId?limit=1&sort=-timeStamp')
+      httpBackend.when('GET', 'http://example.com:1234/jvm-gc/0.0.3/delta/foo-jvmId')
         .respond(expected);
       svc.getJvmGcData('foo-jvmId').then(res => {
-        res.data.should.deepEqual(expected);
+        res.should.deepEqual(expected.response);
         done();
       });
-      httpBackend.expectGET('http://example.com:1234/jvm-gc/0.0.3/jvms/foo-jvmId?limit=1&sort=-timeStamp');
+      httpBackend.expectGET('http://example.com:1234/jvm-gc/0.0.3/delta/foo-jvmId');
       httpBackend.flush();
       scope.$apply();
     });
+  });
 
-    it('should allow overriding limit', done => {
+  describe('getSnapshot (jvmId, collectorName)', () => {
+    it('should resolve mock data', done => {
       let expected = {
-        metaspace: 100
+        response: [
+          {
+            jvmId: 'foo-jvmId',
+            collectorName: 'foo-collector',
+            timeStamp: { $numberLong: '1234' },
+            wallTimeDelta: { $numberLong: '8888' },
+            wallTimeInMicros: { $numberLong: '9999' }
+          }
+        ]
       };
-      httpBackend.when('GET', 'http://example.com:1234/jvm-gc/0.0.3/jvms/foo-jvmId?limit=5&sort=-timeStamp')
+      httpBackend.when('GET', 'http://example.com:1234/jvm-gc/0.0.3/jvms/foo-jvmId?limit=1&query=collectorName%3D%3Dfoo-collector&sort=-timeStamp')
         .respond(expected);
-      svc.getJvmGcData('foo-jvmId', 5).then(res => {
-        res.data.should.deepEqual(expected);
+      svc.getSnapshot('foo-jvmId', 'foo-collector').then(res => {
+        res.should.deepEqual(expected.response[0].wallTimeInMicros);
         done();
       });
-      httpBackend.expectGET('http://example.com:1234/jvm-gc/0.0.3/jvms/foo-jvmId?limit=5&sort=-timeStamp');
-      httpBackend.flush();
-      scope.$apply();
-    });
-
-    it('should allow specifying collectorName', done => {
-      let expected = {
-        metaspace: 100
-      };
-      httpBackend.when('GET', 'http://example.com:1234/jvm-gc/0.0.3/jvms/foo-jvmId?limit=5&query=collectorName%3D%3DfooCollector&sort=-timeStamp')
-        .respond(expected);
-      svc.getJvmGcData('foo-jvmId', 5, 'fooCollector').then(res => {
-        res.data.should.deepEqual(expected);
-        done();
-      });
-      httpBackend.expectGET('http://example.com:1234/jvm-gc/0.0.3/jvms/foo-jvmId?limit=5&query=collectorName%3D%3DfooCollector&sort=-timeStamp');
+      httpBackend.expectGET('http://example.com:1234/jvm-gc/0.0.3/jvms/foo-jvmId?limit=1&query=collectorName%3D%3Dfoo-collector&sort=-timeStamp');
       httpBackend.flush();
       scope.$apply();
     });