# HG changeset patch # User Andrew Azores # Date 1506349881 14400 # Node ID fc747d2c2f6a7b53b149cefc25c937b71d3bc088 # Parent 6568fdab115d6739a8a82959e71089d539efe135 Add Byteman metrics view Reviewed-by: jkang Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2017-September/025171.html diff -r 6568fdab115d -r fc747d2c2f6a mock-api/endpoints/jvm-byteman.endpoint.js --- a/mock-api/endpoints/jvm-byteman.endpoint.js Mon Sep 25 10:31:07 2017 -0400 +++ b/mock-api/endpoints/jvm-byteman.endpoint.js Mon Sep 25 10:31:21 2017 -0400 @@ -24,6 +24,34 @@ } )); }); + server.app.get('/jvm-byteman/0.0.1/metrics/jvms/:jvmId', function (req, res) { + server.logRequest('jvm-byteman', req); + + var jvmId = req.params.jvmId; + + var response = []; + response.push({ + agentId: 'foo-agentId', + jvmId: jvmId, + timeStamp: { $numberLong: Date.now().toString() }, + marker: 'foo-marker', + payload: '{"action":"ExampleClass.method() called"}' + }); + response.push({ + agentId: 'foo-agentId', + jvmId: jvmId, + timeStamp: { $numberLong: Date.now().toString() }, + marker: 'rand-marker', + payload: { doubleKey: Math.random() } + }); + + res.setHeader('Content-Type', 'application/json'); + res.send(JSON.stringify( + { + response: response + } + )); + }); // command channel server.init('byteman-command'); diff -r 6568fdab115d -r fc747d2c2f6a package.json --- a/package.json Mon Sep 25 10:31:07 2017 -0400 +++ b/package.json Mon Sep 25 10:31:21 2017 -0400 @@ -15,6 +15,7 @@ "angular-patternfly": "^4.4.1", "angular-translate": "^2.15.2", "angular-translate-interpolation-messageformat": "^2.15.2", + "angularjs-datatables": "^0.5.9", "babel-core": "^6.24.0", "babel-loader": "^7.0.0", "babel-plugin-angularjs-annotate": "^0.7.0", diff -r 6568fdab115d -r fc747d2c2f6a src/app/app.module.js --- a/src/app/app.module.js Mon Sep 25 10:31:07 2017 -0400 +++ b/src/app/app.module.js Mon Sep 25 10:31:21 2017 -0400 @@ -33,10 +33,17 @@ import 'bootstrap'; import 'bootstrap-switch'; +import 'angular-patternfly/node_modules/patternfly/node_modules/jquery/dist/jquery.js'; +import 'angular-patternfly/node_modules/patternfly/node_modules/datatables.net/js/jquery.dataTables.js'; +import 'angular-patternfly/node_modules/patternfly/node_modules/datatables.net-select/js/dataTables.select.js'; +import 'angularjs-datatables/dist/angular-datatables.min.js'; +import 'angularjs-datatables/dist/plugins/select/angular-datatables.select.min.js'; + import {default as authModule, config as authModBootstrap} from 'components/auth/auth.module.js'; import authInterceptorFactory from './auth-interceptor.factory.js'; require.ensure([], () => { + require('angular-patternfly/node_modules/datatables.net-dt/css/jquery.dataTables.css'); require('angular-patternfly/node_modules/patternfly/dist/css/patternfly.css'); require('angular-patternfly/node_modules/patternfly/dist/css/patternfly-additions.css'); require('bootstrap-switch/dist/css/bootstrap3/bootstrap-switch.css'); @@ -50,6 +57,7 @@ 'ui.bootstrap', 'patternfly', 'patternfly.navigation', + 'patternfly.table', angularTranslate, authModule, // non-core modules diff -r 6568fdab115d -r fc747d2c2f6a src/app/components/jvm-info/byteman/byteman.html --- a/src/app/components/jvm-info/byteman/byteman.html Mon Sep 25 10:31:07 2017 -0400 +++ b/src/app/components/jvm-info/byteman/byteman.html Mon Sep 25 10:31:21 2017 -0400 @@ -1,6 +1,7 @@
diff -r 6568fdab115d -r fc747d2c2f6a src/app/components/jvm-info/byteman/byteman.service.js --- a/src/app/components/jvm-info/byteman/byteman.service.js Mon Sep 25 10:31:07 2017 -0400 +++ b/src/app/components/jvm-info/byteman/byteman.service.js Mon Sep 25 10:31:21 2017 -0400 @@ -32,6 +32,7 @@ const LOAD_RULE_ACTION = 0; const UNLOAD_RULE_ACTION = 1; const INITIAL_LISTEN_PORT = -1; +const METRICS_QUERY_LIMIT = 0; class BytemanService { @@ -64,6 +65,37 @@ return this._getJvmInfo(systemId, jvmId).then(res => res.mainClass); } + getMetrics (jvmId, oldestLimit) { + return this._http.get(urlJoin(this._gatewayUrl, 'jvm-byteman', '0.0.1', 'metrics', 'jvms', jvmId), { + params: { + query: `timeStamp>=${oldestLimit}`, + sort: '-timeStamp', + limit: METRICS_QUERY_LIMIT + } + }) + .then(res => { + let results = []; + for (let i = 0; i < res.data.response.length; i++) { + let metric = res.data.response[i]; + let payload; + if (typeof metric.payload === 'string') { + payload = JSON.parse(metric.payload); + } else { + payload = metric.payload; + } + let prop = Object.getOwnPropertyNames(payload)[0]; + + results.push({ + timestamp: metric.timeStamp, + marker: metric.marker, + name: prop, + value: payload[prop] + }); + } + return results; + }); + } + _sendCmdChanRequest (systemId, jvmId, action, rule) { let defer = this._q.defer(); diff -r 6568fdab115d -r fc747d2c2f6a src/app/components/jvm-info/byteman/byteman.service.spec.js --- a/src/app/components/jvm-info/byteman/byteman.service.spec.js Mon Sep 25 10:31:07 2017 -0400 +++ b/src/app/components/jvm-info/byteman/byteman.service.spec.js Mon Sep 25 10:31:21 2017 -0400 @@ -265,5 +265,59 @@ }); }); + describe('getMetrics (jvmId, oldestLimit)', () => { + it('should resolve mock data when payload is a string type', done => { + let response = { + response: [ + { + timeStamp: { $numberLong: '5000' }, + marker: 'foo-marker', + payload: '{"action":"foo-method called"}' + } + ] + }; + httpBackend.when('GET', 'http://example.com:1234/jvm-byteman/0.0.1/metrics/jvms/foo-systemId?limit=0&query=timeStamp%3E%3D6789&sort=-timeStamp') + .respond(response); + svc.getMetrics('foo-systemId', 6789).then(res => { + res.should.deepEqual([{ + timestamp: { $numberLong: '5000' }, + marker: 'foo-marker', + name: 'action', + value: 'foo-method called' + }]); + done(); + }); + httpBackend.expectGET('http://example.com:1234/jvm-byteman/0.0.1/metrics/jvms/foo-systemId?limit=0&query=timeStamp%3E%3D6789&sort=-timeStamp'); + httpBackend.flush(); + scope.$apply(); + }); + + it('should resolve mock data when payload is a non-string type', done => { + let response = { + response: [ + { + timeStamp: { $numberLong: '5000' }, + marker: 'foo-marker', + payload: { doubleKey: 0.125 } + } + ] + }; + httpBackend.when('GET', 'http://example.com:1234/jvm-byteman/0.0.1/metrics/jvms/foo-systemId?limit=0&query=timeStamp%3E%3D6789&sort=-timeStamp') + .respond(response); + svc.getMetrics('foo-systemId', 6789).then(res => { + res.should.deepEqual([{ + timestamp: { $numberLong: '5000' }, + marker: 'foo-marker', + name: 'doubleKey', + value: 0.125 + }]); + done(); + }); + httpBackend.expectGET('http://example.com:1234/jvm-byteman/0.0.1/metrics/jvms/foo-systemId?limit=0&query=timeStamp%3E%3D6789&sort=-timeStamp'); + httpBackend.flush(); + scope.$apply(); + }); + }); + }); diff -r 6568fdab115d -r fc747d2c2f6a src/app/components/jvm-info/byteman/en.locale.yaml --- a/src/app/components/jvm-info/byteman/en.locale.yaml Mon Sep 25 10:31:07 2017 -0400 +++ b/src/app/components/jvm-info/byteman/en.locale.yaml Mon Sep 25 10:31:21 2017 -0400 @@ -1,2 +1,3 @@ byteman: RULES_VIEW: Rules + METRICS_VIEW: Metrics diff -r 6568fdab115d -r fc747d2c2f6a src/app/components/jvm-info/byteman/metrics/byteman-metrics.component.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/app/components/jvm-info/byteman/metrics/byteman-metrics.component.js Mon Sep 25 10:31:21 2017 -0400 @@ -0,0 +1,40 @@ +/** + * 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 BytemanMetricsController from './byteman-metrics.controller.js'; + +export default angular + .module('byteman.metrics', [ + BytemanMetricsController, + 'patternfly', + 'patternfly.table' + ]) + .component('bytemanMetrics', { + controller: 'BytemanMetricsController', + template: require('./byteman-metrics.html') + }) + .name; diff -r 6568fdab115d -r fc747d2c2f6a src/app/components/jvm-info/byteman/metrics/byteman-metrics.controller.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/app/components/jvm-info/byteman/metrics/byteman-metrics.controller.js Mon Sep 25 10:31:21 2017 -0400 @@ -0,0 +1,104 @@ +/** + * 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 service from '../byteman.service.js'; +import _ from 'lodash'; + +const REFRESH_RATE = 5000; // five seconds +const MAX_DATA_AGE = 600000; // ten minutes; + +class BytemanMetricsController { + constructor ($stateParams, $translate, $interval, bytemanService, + metricToNumberFilter, timestampToDateFilter) { + 'ngInject'; + this.jvmId = $stateParams.jvmId; + this._translate = $translate; + this._interval = $interval; + this._svc = bytemanService; + this._metricToNumber = metricToNumberFilter; + + this.config = { + selectionMatchProp: 'timestamp', + showCheckBoxes: false, + itemsAvailable: false + }; + + this.items = []; + + this._dataAgeLimit = MAX_DATA_AGE; + + this._translate([ + 'byteman.metrics.TIMESTAMP_COL_HEADER', + 'byteman.metrics.MARKER_COL_HEADER', + 'byteman.metrics.NAME_COL_HEADER', + 'byteman.metrics.VALUE_COL_HEADER' + ]).then(translations => { + this.columns = [ + { + itemField: 'timestamp', + header: translations['byteman.metrics.TIMESTAMP_COL_HEADER'], + templateFn: timestampToDateFilter + }, + { itemField: 'marker', header: translations['byteman.metrics.MARKER_COL_HEADER'] }, + { itemField: 'name', header: translations['byteman.metrics.NAME_COL_HEADER'] }, + { itemField: 'value', header: translations['byteman.metrics.VALUE_COL_HEADER'] } + ]; + }); + } + + $onInit () { + this._update(); + this._refresh = this._interval(() => this._update(), REFRESH_RATE); + } + + $onDestroy () { + if (angular.isDefined(this._refresh)) { + this._interval.cancel(this._refresh); + } + } + + set dataAgeLimit (val) { + this._dataAgeLimit = val; + this._update(); + } + + get dataAgeLimit () { + return this._dataAgeLimit.toString(); + } + + _update () { + this._svc.getMetrics(this.jvmId, Date.now() - this._dataAgeLimit).then(res => { + this.items = res; + this.config.itemsAvailable = true; + }); + } +} + +export default angular + .module('byteman.metrics.controller', [service]) + .controller('BytemanMetricsController', BytemanMetricsController) + .name; diff -r 6568fdab115d -r fc747d2c2f6a src/app/components/jvm-info/byteman/metrics/byteman-metrics.controller.spec.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/app/components/jvm-info/byteman/metrics/byteman-metrics.controller.spec.js Mon Sep 25 10:31:21 2017 -0400 @@ -0,0 +1,149 @@ +/** + * 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 './byteman-metrics.controller.js'; + +describe('BytemanMetricsController', () => { + + let ctrl, translate, interval, svc, metricToNumber, timestampToDate; + beforeEach(() => { + angular.mock.module(controllerModule); + + translate = sinon.stub().returns({ + then: sinon.stub().yields({ + 'byteman.metrics.TIMESTAMP_COL_HEADER': 'Time Stamp', + 'byteman.metrics.MARKER_COL_HEADER': 'Marker', + 'byteman.metrics.NAME_COL_HEADER': 'Name', + 'byteman.metrics.VALUE_COL_HEADER': 'Value' + }) + }); + + interval = sinon.stub().returns('interval-mock'); + interval.cancel = sinon.spy(); + + svc = {}; + svc.then = sinon.stub(); + svc.getMetrics = sinon.stub().returns({ then: svc.then }); + + metricToNumber = sinon.stub().returns(123); + + timestampToDate = sinon.stub().returns('mock date'); + + let timestamp = Date.now(); + sinon.stub(Date, 'now').returns(timestamp); + + angular.mock.inject($controller => { + 'ngInject'; + ctrl = $controller('BytemanMetricsController', { + $stateParams: { jvmId: 'foo-jvmId' }, + $translate: translate, + $interval: interval, + bytemanService: svc, + metricToNumberFilter: metricToNumber, + timestampToDateFilter: timestampToDate + }); + }); + }); + + afterEach(() => { + Date.now.restore(); + }); + + describe('$onInit ()', () => { + it('should start updating', () => { + interval.should.not.be.called(); + svc.getMetrics.should.not.be.called(); + + ctrl.$onInit(); + + interval.should.be.calledOnce(); + interval.should.be.calledWith(sinon.match.func, 5000); + ctrl.should.have.ownProperty('_refresh'); + ctrl._refresh.should.equal('interval-mock'); + svc.getMetrics.should.be.calledOnce(); + + let updateFn = interval.firstCall.args[0]; + updateFn(); + svc.getMetrics.should.be.calledTwice(); + }); + }); + + describe('$onDestroy ()', () => { + it('should cancel refresh if started', () => { + interval.cancel.should.not.be.called(); + ctrl.$onInit(); + interval.cancel.should.not.be.called(); + ctrl.$onDestroy(); + interval.cancel.should.be.calledOnce(); + interval.cancel.should.be.calledWith('interval-mock'); + }); + + it('should do nothing if not started', () => { + interval.cancel.should.not.be.called(); + ctrl.$onDestroy(); + interval.cancel.should.not.be.called(); + }); + }); + + describe('dataAgeLimit', () => { + it('should trigger update with new limit', () => { + svc.getMetrics.should.not.be.called(); + Date.now.returns(100000); + ctrl.dataAgeLimit = '30000'; + svc.getMetrics.should.be.calledWith('foo-jvmId', 100000 - 30000); + }); + + it('should reflect in getter', () => { + ctrl.dataAgeLimit = '30000'; + ctrl.dataAgeLimit.should.equal('30000'); + }); + }); + + describe('_update ()', () => { + it('should use jvmId and current time minus age limit', () => { + Date.now.returns(100000); + ctrl._dataAgeLimit = 30000; + ctrl._update(); + svc.getMetrics.should.be.calledOnce(); + svc.getMetrics.should.be.calledWith('foo-jvmId', 100000 - 30000); + }); + + it('should set items from service', () => { + let items = [{ + timestamp: 72000, + marker: 'foo-marker', + name: 'action', + value: 'foo-method called' + }]; + svc.then.yields(items); + ctrl._update(); + ctrl.items.should.deepEqual(items); + ctrl.config.itemsAvailable.should.be.true(); + }); + }); + +}); diff -r 6568fdab115d -r fc747d2c2f6a src/app/components/jvm-info/byteman/metrics/byteman-metrics.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/app/components/jvm-info/byteman/metrics/byteman-metrics.html Mon Sep 25 10:31:21 2017 -0400 @@ -0,0 +1,16 @@ +
+ + +
+ + diff -r 6568fdab115d -r fc747d2c2f6a src/app/components/jvm-info/byteman/metrics/byteman-metrics.routing.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/app/components/jvm-info/byteman/metrics/byteman-metrics.routing.js Mon Sep 25 10:31:21 2017 -0400 @@ -0,0 +1,57 @@ +/** + * 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. + */ + +function config ($stateProvider) { + 'ngInject'; + + $stateProvider.state('jvmInfo.byteman.metrics', { + url: '/metrics', + component: 'bytemanMetrics', + resolve: { + lazyLoad: ($q, $ocLazyLoad) => { + 'ngInject'; + return $q(resolve => { + require.ensure(['./byteman-metrics.component.js'], () => { + let module = require('./byteman-metrics.component.js'); + $ocLazyLoad.load({ name: module.default }); + resolve(module); + }); + }); + } + } + }); +} + +export { config }; + +export default angular + .module('byteman.metrics.routing', [ + 'ui.router', + 'oc.lazyLoad' + ]) + .config(config) + .name; diff -r 6568fdab115d -r fc747d2c2f6a src/app/components/jvm-info/byteman/metrics/byteman-metrics.routing.spec.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/app/components/jvm-info/byteman/metrics/byteman-metrics.routing.spec.js Mon Sep 25 10:31:21 2017 -0400 @@ -0,0 +1,77 @@ +/** + * 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. + */ + +describe('BytemanMetricsRouting', () => { + + let module = require('./byteman-metrics.routing.js'); + let stateProvider, args, q, ocLazyLoad; + beforeEach(() => { + angular.mock.module(module.default); + stateProvider = { + state: sinon.spy() + }; + module.config(stateProvider); + args = stateProvider.state.args[0]; + q = sinon.spy(); + ocLazyLoad = { + load: sinon.spy() + }; + }); + + describe('stateProvider', () => { + it('should call $stateProvider.state', () => { + stateProvider.state.should.be.calledOnce(); + }); + + it('should define a \'jvmInfo.byteman.metrics\' state', () => { + args[0].should.equal('jvmInfo.byteman.metrics'); + }); + + it('should map to /metrics', () => { + args[1].url.should.equal('/metrics'); + }); + + it('resolve should load byteman-metrics component', done => { + let resolveFn = args[1].resolve.lazyLoad[2]; + resolveFn.should.be.a.Function(); + resolveFn(q, ocLazyLoad); + q.should.be.calledOnce(); + + let deferred = q.args[0][0]; + deferred.should.be.a.Function(); + + let resolve = sinon.stub().callsFake(val => { + let mod = require('./byteman-metrics.component.js'); + ocLazyLoad.load.should.be.calledWith({ name: mod.default }); + val.should.equal(mod); + done(); + }); + deferred(resolve); + }); + }); + +}); diff -r 6568fdab115d -r fc747d2c2f6a src/app/components/jvm-info/byteman/metrics/en.locale.yaml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/app/components/jvm-info/byteman/metrics/en.locale.yaml Mon Sep 25 10:31:21 2017 -0400 @@ -0,0 +1,11 @@ +byteman: + metrics: + TIMESTAMP_COL_HEADER: Time Stamp + MARKER_COL_HEADER: Marker + NAME_COL_HEADER: Name + VALUE_COL_HEADER: Value + + MAX_DATA_AGE_LABEL: Max Data Age + dataAge: + SECONDS: '{SECONDS, plural, =0{0 Seconds} one{1 Second} other{# Seconds}}{DEFAULT, select, true{ (Default)} other{}}' + MINUTES: '{MINUTES, plural, =0{0 Minutes} one{1 Minute} other{# Minutes}}{DEFAULT, select, true{ (Default)} other{}}'