From dce9de7a9d217f3069653e6f964e0038eb640c25 Mon Sep 17 00:00:00 2001 From: CaptainIRS <36656347+CaptainIRS@users.noreply.github.com> Date: Wed, 21 Sep 2022 11:06:29 +0530 Subject: [PATCH 1/3] Add TxObserver for Prometheus manager Signed-off-by: CaptainIRS <36656347+CaptainIRS@users.noreply.github.com> --- .../messages/prometheusUpdateMessage.js | 37 +++ .../prometheus-manager-tx-observer.js | 97 ++++++++ .../prometheus-manager-tx-observer.js | 214 ++++++++++++++++++ 3 files changed, 348 insertions(+) create mode 100644 packages/caliper-core/lib/common/messages/prometheusUpdateMessage.js create mode 100644 packages/caliper-core/lib/worker/tx-observers/prometheus-manager-tx-observer.js create mode 100644 packages/caliper-core/test/worker/tx-observers/prometheus-manager-tx-observer.js diff --git a/packages/caliper-core/lib/common/messages/prometheusUpdateMessage.js b/packages/caliper-core/lib/common/messages/prometheusUpdateMessage.js new file mode 100644 index 000000000..b11513370 --- /dev/null +++ b/packages/caliper-core/lib/common/messages/prometheusUpdateMessage.js @@ -0,0 +1,37 @@ +/* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +const Message = require('./message'); +const MessageTypes = require('./../utils/constants').Messages.Types; + +/** + * Class for the "prometheusUpdate" message. + */ +class PrometheusUpdateMessage extends Message { + /** + * Constructor for a "prometheusUpdate" message instance. + * @param {string} sender The sender of the message. + * @param {string[]} recipients The recipients of the message. + * @param {object} content The content of the message. + * @param {string} date The date string of the message. + * @param {string} error The potential error associated with the message. + */ + constructor(sender, recipients, content, date = undefined, error = undefined) { + super(sender, recipients, MessageTypes.PrometheusUpdate, content, date, error); + } +} + +module.exports = PrometheusUpdateMessage; diff --git a/packages/caliper-core/lib/worker/tx-observers/prometheus-manager-tx-observer.js b/packages/caliper-core/lib/worker/tx-observers/prometheus-manager-tx-observer.js new file mode 100644 index 000000000..46ba85b4e --- /dev/null +++ b/packages/caliper-core/lib/worker/tx-observers/prometheus-manager-tx-observer.js @@ -0,0 +1,97 @@ +/* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +const TxObserverInterface = require('./tx-observer-interface'); + +const PrometheusUpdateMessage = require('../../common/messages/prometheusUpdateMessage'); + +/** + * Prometheus Manager TX observer used to send updates to the Prometheus scrape server in the manager. + */ +class PrometheusManagerTxObserver extends TxObserverInterface { + /** + * Initializes the instance. + * @param {object} options The observer configuration object. + * @param {MessengerInterface} messenger The worker messenger instance. Not used. + * @param {number} workerIndex The 0-based index of the worker node. + * @param {string} managerUuid The UUID of the manager messenger. + */ + constructor(options, messenger, workerIndex, managerUuid) { + super(messenger, workerIndex); + + this.managerUuid = managerUuid; + } + /** + * Called when TXs are submitted. + * @param {number} count The number of submitted TXs. Can be greater than one for a batch of TXs. + */ + txSubmitted(count) { + const message = new PrometheusUpdateMessage(this.messenger.getUUID(), [this.managerUuid], { + event: 'txSubmitted', + workerIndex: this.workerIndex, + roundIndex: this.currentRound, + roundLabel: this.roundLabel, + count: count + }); + this.messenger.send(message); + } + + /** + * Called when TXs are finished. + * @param {TxStatus | TxStatus[]} results The result information of the finished TXs. Can be a collection of results for a batch of TXs. + */ + txFinished(results) { + if (Array.isArray(results)) { + for (const result of results) { + // pass/fail status from result.GetStatus() + const message = new PrometheusUpdateMessage(this.messenger.getUUID(), [this.managerUuid], { + event: 'txFinished', + workerIndex: this.workerIndex, + roundIndex: this.currentRound, + roundLabel: this.roundLabel, + status: result.GetStatus(), + latency: result.GetTimeFinal() - result.GetTimeCreate() + }); + this.messenger.send(message); + } + } else { + // pass/fail status from result.GetStatus() + const message = new PrometheusUpdateMessage(this.messenger.getUUID(), [this.managerUuid], { + event: 'txFinished', + workerIndex: this.workerIndex, + roundIndex: this.currentRound, + roundLabel: this.roundLabel, + status: results.GetStatus(), + latency: (results.GetTimeFinal() - results.GetTimeCreate())/1000 + }); + this.messenger.send(message); + } + } +} + +/** + * Factory function for creating a PrometheusManagerTxObserver instance. + * @param {object} options The observer configuration object. + * @param {MessengerInterface} messenger The worker messenger instance. + * @param {number} workerIndex The 0-based index of the worker node. + * @param {string} managerUuid The UUID of the manager messenger. + * @return {TxObserverInterface} The observer instance. + */ +function createTxObserver(options, messenger, workerIndex, managerUuid) { + return new PrometheusManagerTxObserver(options, messenger, workerIndex, managerUuid); +} + +module.exports.createTxObserver = createTxObserver; diff --git a/packages/caliper-core/test/worker/tx-observers/prometheus-manager-tx-observer.js b/packages/caliper-core/test/worker/tx-observers/prometheus-manager-tx-observer.js new file mode 100644 index 000000000..962ac4bda --- /dev/null +++ b/packages/caliper-core/test/worker/tx-observers/prometheus-manager-tx-observer.js @@ -0,0 +1,214 @@ +/* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +const chai = require('chai'); +const chaiAsPromised = require('chai-as-promised'); +chai.use(chaiAsPromised); +const mockery = require('mockery'); +const sinon = require('sinon'); + +/** + * simulate Util + */ +class Utils { + /** + * + * @param {*} path path + * @return {string} the fake path + */ + static resolvePath(path) { + return 'fake/path'; + } + + /** + * + * @return {boolean} the fake path + */ + static isForkedProcess() { + return false; + } + + /** + * + * @param {*} yaml res + * @return {string} the fake yaml + */ + static parseYaml(yaml) { + return 'yaml'; + } + + /** + * @returns {*} logger stub + */ + static getLogger() { + return { + debug: sinon.stub(), + error: sinon.stub() + }; + } + + /** + * @param {*} url url + * @returns {*} url + */ + static augmentUrlWithBasicAuth(url) { + return url; + } +} + +mockery.enable({ + warnOnReplace: false, + warnOnUnregistered: false +}); +mockery.registerMock('../../common/utils/caliper-utils', Utils); + + +describe('When using a PrometheusManagerTxObserver', () => { + + // Require here to enable mocks to be established + const PrometheusManagerTxObserver = require('../../../lib/worker/tx-observers/prometheus-manager-tx-observer'); + + after(()=> { + mockery.deregisterAll(); + mockery.disable(); + }); + + it('should set managerUuid passed through constructor', () => { + const observer = new PrometheusManagerTxObserver.createTxObserver(undefined, undefined, undefined, 'fakeUuid'); + observer.managerUuid.should.equal('fakeUuid'); + }); + + + it('should send update message when TXs are submitted', () => { + const senderUuid = 'senderUuid'; + const messenger = { + getUUID: sinon.stub().returns(senderUuid), + send: sinon.spy() + }; + const workerIndex = 0; + const roundIndex = 1; + const roundLabel = 'roundLabel'; + const managerUuid = 'managerUuid'; + const txCount = 1; + + const observer = new PrometheusManagerTxObserver.createTxObserver(undefined, messenger, workerIndex, managerUuid); + observer.messenger = messenger; + observer.currentRound = roundIndex; + observer.roundLabel = roundLabel; + + observer.txSubmitted(txCount); + + sinon.assert.calledWith(messenger.send, sinon.match({ + content: sinon.match({ + event: 'txSubmitted', + roundIndex: roundIndex, + roundLabel: roundLabel, + count: txCount, + }), + sender: senderUuid, + recipients: sinon.match.array.contains([managerUuid]) + })); + }); + + it('should send update message when single TX is finished', () => { + const senderUuid = 'senderUuid'; + const messenger = { + getUUID: sinon.stub().returns(senderUuid), + send: sinon.spy() + }; + const workerIndex = 0; + const roundIndex = 1; + const roundLabel = 'roundLabel'; + const managerUuid = 'managerUuid'; + const timeFinal = 1000; + const timeCreate = 0; + + const result = { + GetStatus: sinon.stub().returns('success'), + GetTimeFinal: sinon.stub().returns(timeFinal), + GetTimeCreate: sinon.stub().returns(timeCreate), + }; + + const observer = new PrometheusManagerTxObserver.createTxObserver(undefined, messenger, workerIndex, managerUuid); + observer.messenger = messenger; + observer.currentRound = roundIndex; + observer.roundLabel = roundLabel; + + observer.txFinished(result); + + sinon.assert.calledWith(messenger.send, sinon.match({ + content: sinon.match({ + event: 'txFinished', + roundIndex: roundIndex, + roundLabel: roundLabel, + status: 'success', + latency: (timeFinal - timeCreate) / 1000, + }), + sender: senderUuid, + recipients: sinon.match.array.contains([managerUuid]) + })); + }); + + it('should send update message when multiple TXs are finished', () => { + const senderUuid = 'senderUuid'; + const messenger = { + getUUID: sinon.stub().returns(senderUuid), + send: sinon.spy() + }; + const workerIndex = 0; + const roundIndex = 1; + const roundLabel = 'roundLabel'; + const managerUuid = 'managerUuid'; + const timeFinal = 1000; + const timeCreate = 0; + + const result = { + GetStatus: sinon.stub().returns('success'), + GetTimeFinal: sinon.stub().returns(timeFinal), + GetTimeCreate: sinon.stub().returns(timeCreate), + }; + + const observer = new PrometheusManagerTxObserver.createTxObserver(undefined, messenger, workerIndex, managerUuid); + observer.messenger = messenger; + observer.currentRound = roundIndex; + observer.roundLabel = roundLabel; + + observer.txFinished([result, result]); + + sinon.assert.calledWith(messenger.send, sinon.match({ + content: sinon.match({ + event: 'txFinished', + roundIndex: roundIndex, + roundLabel: roundLabel, + status: 'success', + latency: (timeFinal - timeCreate), + }), + sender: senderUuid, + recipients: sinon.match.array.contains([managerUuid]) + })); + sinon.assert.calledWith(messenger.send, sinon.match({ + content: sinon.match({ + event: 'txFinished', + roundIndex: roundIndex, + roundLabel: roundLabel, + status: 'success', + latency: (timeFinal - timeCreate), + }), + sender: senderUuid, + recipients: sinon.match.array.contains([managerUuid]) + })); + }); +}); From 0ded27567c017cca3fd4627c1eaf9cf91eb02fee Mon Sep 17 00:00:00 2001 From: CaptainIRS <36656347+CaptainIRS@users.noreply.github.com> Date: Sun, 25 Sep 2022 20:42:08 +0530 Subject: [PATCH 2/3] Address review comments * Rename prometheusUpdateMessage to workerMetricsMessage * Fix doc comment in test * Add collate and periodic update methods * Add logs for config property errors * Fix histogram calculation Signed-off-by: CaptainIRS <36656347+CaptainIRS@users.noreply.github.com> --- .../caliper-core/lib/common/config/Config.js | 5 + .../lib/common/config/default.yaml | 7 + ...dateMessage.js => workerMetricsMessage.js} | 12 +- .../prometheus-manager-tx-observer.js | 93 +++- .../prometheus-manager-tx-observer.js | 523 ++++++++++++++++-- 5 files changed, 570 insertions(+), 70 deletions(-) rename packages/caliper-core/lib/common/messages/{prometheusUpdateMessage.js => workerMetricsMessage.js} (74%) diff --git a/packages/caliper-core/lib/common/config/Config.js b/packages/caliper-core/lib/common/config/Config.js index ff8a52f30..f4dd111d5 100644 --- a/packages/caliper-core/lib/common/config/Config.js +++ b/packages/caliper-core/lib/common/config/Config.js @@ -65,6 +65,11 @@ const keys = { }, PrometheusPush: { Interval: 'caliper-observer-prometheuspush-interval' + }, + PrometheusManager: { + Method: 'caliper-observer-prometheusmanager-method', + Interval: 'caliper-observer-prometheusmanager-interval', + CollationCount: 'caliper-observer-prometheusmanager-collationcount' } }, Workspace: 'caliper-workspace', diff --git a/packages/caliper-core/lib/common/config/default.yaml b/packages/caliper-core/lib/common/config/default.yaml index 1bd236ba7..c83f2f3d7 100644 --- a/packages/caliper-core/lib/common/config/default.yaml +++ b/packages/caliper-core/lib/common/config/default.yaml @@ -90,6 +90,13 @@ caliper: prometheus: # Default scrape port for prometheus tx observer scrapeport: 3000 + prometheusmanager: + # Update method + method: periodic + # Default update interval for the periodic update method + interval: 1000 + # Collation count for the collate update method + collationcount: 10 # Configurations related to the logging mechanism logging: # Specifies the message structure through placeholders diff --git a/packages/caliper-core/lib/common/messages/prometheusUpdateMessage.js b/packages/caliper-core/lib/common/messages/workerMetricsMessage.js similarity index 74% rename from packages/caliper-core/lib/common/messages/prometheusUpdateMessage.js rename to packages/caliper-core/lib/common/messages/workerMetricsMessage.js index b11513370..ca36748dc 100644 --- a/packages/caliper-core/lib/common/messages/prometheusUpdateMessage.js +++ b/packages/caliper-core/lib/common/messages/workerMetricsMessage.js @@ -15,14 +15,14 @@ 'use strict'; const Message = require('./message'); -const MessageTypes = require('./../utils/constants').Messages.Types; +const MessageTypes = require('../utils/constants').Messages.Types; /** - * Class for the "prometheusUpdate" message. + * Class for the "workerMetricsMessage" message. */ -class PrometheusUpdateMessage extends Message { +class WorkerMetricsMessage extends Message { /** - * Constructor for a "prometheusUpdate" message instance. + * Constructor for a "workerMetricsMessage" message instance. * @param {string} sender The sender of the message. * @param {string[]} recipients The recipients of the message. * @param {object} content The content of the message. @@ -30,8 +30,8 @@ class PrometheusUpdateMessage extends Message { * @param {string} error The potential error associated with the message. */ constructor(sender, recipients, content, date = undefined, error = undefined) { - super(sender, recipients, MessageTypes.PrometheusUpdate, content, date, error); + super(sender, recipients, MessageTypes.WorkerMetricsMessage, content, date, error); } } -module.exports = PrometheusUpdateMessage; +module.exports = WorkerMetricsMessage; diff --git a/packages/caliper-core/lib/worker/tx-observers/prometheus-manager-tx-observer.js b/packages/caliper-core/lib/worker/tx-observers/prometheus-manager-tx-observer.js index 46ba85b4e..2fea7f3bd 100644 --- a/packages/caliper-core/lib/worker/tx-observers/prometheus-manager-tx-observer.js +++ b/packages/caliper-core/lib/worker/tx-observers/prometheus-manager-tx-observer.js @@ -15,8 +15,11 @@ 'use strict'; const TxObserverInterface = require('./tx-observer-interface'); +const WorkerMetricsMessage = require('../../common/messages/workerMetricsMessage'); +const CaliperUtils = require('../../common/utils/caliper-utils'); +const ConfigUtil = require('../../common/config/config-util'); -const PrometheusUpdateMessage = require('../../common/messages/prometheusUpdateMessage'); +const Logger = CaliperUtils.getLogger('prometheus-tx-observer'); /** * Prometheus Manager TX observer used to send updates to the Prometheus scrape server in the manager. @@ -32,6 +35,30 @@ class PrometheusManagerTxObserver extends TxObserverInterface { constructor(options, messenger, workerIndex, managerUuid) { super(messenger, workerIndex); + this.method = (options && options.method) ? options.method : ConfigUtil.get(ConfigUtil.keys.Observer.PrometheusManager.Method); + if (this.method === 'periodic') { + this.updateInterval = (options && options.interval) ? options.interval : ConfigUtil.get(ConfigUtil.keys.Observer.PrometheusManager.Interval); + this.intervalObject = undefined; + if (this.updateInterval <= 0) { + Logger.error('Invalid update interval specified, using default value'); + this.updateInterval = ConfigUtil.get(ConfigUtil.keys.Observer.PrometheusManager.Interval); + } + if (options && options.collationCount) { + Logger.warn('Collation count is ignored when using periodic method'); + } + } else if (this.method === 'collate') { + this.collationCount = (options && options.collationCount) ? options.collationCount : ConfigUtil.get(ConfigUtil.keys.Observer.PrometheusManager.CollationCount); + if (this.collationCount <= 0) { + Logger.error('Invalid collation count specified, using default value'); + this.collationCount = ConfigUtil.get(ConfigUtil.keys.Observer.PrometheusManager.CollationCount); + } + if (options && options.interval) { + Logger.warn('Update interval is ignored when using collate method'); + } + } + + this.pendingMessages = []; + this.managerUuid = managerUuid; } /** @@ -39,14 +66,14 @@ class PrometheusManagerTxObserver extends TxObserverInterface { * @param {number} count The number of submitted TXs. Can be greater than one for a batch of TXs. */ txSubmitted(count) { - const message = new PrometheusUpdateMessage(this.messenger.getUUID(), [this.managerUuid], { + const message = new WorkerMetricsMessage(this.messenger.getUUID(), [this.managerUuid], { event: 'txSubmitted', workerIndex: this.workerIndex, roundIndex: this.currentRound, roundLabel: this.roundLabel, count: count }); - this.messenger.send(message); + this._appendMessage(message); } /** @@ -57,28 +84,78 @@ class PrometheusManagerTxObserver extends TxObserverInterface { if (Array.isArray(results)) { for (const result of results) { // pass/fail status from result.GetStatus() - const message = new PrometheusUpdateMessage(this.messenger.getUUID(), [this.managerUuid], { + const message = new WorkerMetricsMessage(this.messenger.getUUID(), [this.managerUuid], { event: 'txFinished', workerIndex: this.workerIndex, roundIndex: this.currentRound, roundLabel: this.roundLabel, status: result.GetStatus(), - latency: result.GetTimeFinal() - result.GetTimeCreate() + latency: (result.GetTimeFinal() - result.GetTimeCreate()) / 1000 }); - this.messenger.send(message); + this._appendMessage(message); } } else { // pass/fail status from result.GetStatus() - const message = new PrometheusUpdateMessage(this.messenger.getUUID(), [this.managerUuid], { + const message = new WorkerMetricsMessage(this.messenger.getUUID(), [this.managerUuid], { event: 'txFinished', workerIndex: this.workerIndex, roundIndex: this.currentRound, roundLabel: this.roundLabel, status: results.GetStatus(), - latency: (results.GetTimeFinal() - results.GetTimeCreate())/1000 + latency: (results.GetTimeFinal() - results.GetTimeCreate()) / 1000 }); + this._appendMessage(message); + } + } + + /** + * Adds message to the pending message queue + * @param {object} message Pending message + * @private + */ + async _appendMessage(message) { + this.pendingMessages.push(message); + + if (this.method === 'collate' && this.pendingMessages.length === this.collationCount) { + await this._sendUpdate(); + } + } + + /** + * Sends the current aggregated statistics to the manager node when triggered by "setInterval". + * @private + */ + async _sendUpdate() { + for (const message of this.pendingMessages) { this.messenger.send(message); } + this.pendingMessages = []; + } + + /** + * Activates the TX observer instance and starts the regular update scheduling. + * @param {number} roundIndex The 0-based index of the current round. + * @param {string} roundLabel The roundLabel name. + */ + async activate(roundIndex, roundLabel) { + await super.activate(roundIndex, roundLabel); + + if (this.method === 'periodic') { + this.intervalObject = setInterval(async () => { await this._sendUpdate(); }, this.updateInterval); + } + } + + /** + * Deactivates the TX observer interface, and stops the regular update scheduling. + */ + async deactivate() { + await super.deactivate(); + + if (this.intervalObject) { + clearInterval(this.intervalObject); + this.intervalObject = undefined; + } + await this._sendUpdate(); } } diff --git a/packages/caliper-core/test/worker/tx-observers/prometheus-manager-tx-observer.js b/packages/caliper-core/test/worker/tx-observers/prometheus-manager-tx-observer.js index 962ac4bda..8645b3d2d 100644 --- a/packages/caliper-core/test/worker/tx-observers/prometheus-manager-tx-observer.js +++ b/packages/caliper-core/test/worker/tx-observers/prometheus-manager-tx-observer.js @@ -19,6 +19,10 @@ const chaiAsPromised = require('chai-as-promised'); chai.use(chaiAsPromised); const mockery = require('mockery'); const sinon = require('sinon'); +const expect = chai.expect; + +const warnLogger = sinon.stub(); +const errorLogger = sinon.stub(); /** * simulate Util @@ -35,7 +39,7 @@ class Utils { /** * - * @return {boolean} the fake path + * @return {boolean} if the process is a forked process */ static isForkedProcess() { return false; @@ -55,8 +59,8 @@ class Utils { */ static getLogger() { return { - debug: sinon.stub(), - error: sinon.stub() + warn: warnLogger, + error: errorLogger }; } @@ -86,17 +90,93 @@ describe('When using a PrometheusManagerTxObserver', () => { mockery.disable(); }); + beforeEach(() => { + warnLogger.reset(); + errorLogger.reset(); + }); + it('should set managerUuid passed through constructor', () => { const observer = new PrometheusManagerTxObserver.createTxObserver(undefined, undefined, undefined, 'fakeUuid'); observer.managerUuid.should.equal('fakeUuid'); }); + it ('should set the correct parameters when method is periodic', () => { + const options = { + method: 'periodic', + interval: 1000, + }; + const observer = new PrometheusManagerTxObserver.createTxObserver(options, undefined, undefined, 'fakeUuid'); + observer.method.should.equal('periodic'); + expect(observer.updateInterval).to.equal(1000); + expect(observer.intervalObject).to.equal(undefined); + }); + + it ('should set the correct parameters when method is collate', () => { + const options = { + method: 'collate', + collationCount: 10, + }; + const observer = new PrometheusManagerTxObserver.createTxObserver(options, undefined, undefined, 'fakeUuid'); + observer.method.should.equal('collate'); + expect(observer.collationCount).to.equal(10); + }); - it('should send update message when TXs are submitted', () => { + it ('should set the default method when options are not provided', () => { + const observer = new PrometheusManagerTxObserver.createTxObserver(undefined, undefined, undefined, 'fakeUuid'); + observer.method.should.equal('periodic'); + expect(observer.updateInterval).to.equal(1000); + expect(observer.intervalObject).to.equal(undefined); + }); + + it ('should use default update interval and print error log when method is periodic and interval is invalid', () => { + const options = { + method: 'periodic', + interval: -1, + }; + const observer = new PrometheusManagerTxObserver.createTxObserver(options, undefined, undefined, 'fakeUuid'); + observer.method.should.equal('periodic'); + expect(observer.updateInterval).to.equal(1000); + expect(observer.intervalObject).to.equal(undefined); + sinon.assert.calledOnce(errorLogger); + }); + + it ('should warn when collationCount is specified but method is periodic', () => { + const options = { + method: 'periodic', + collationCount: 10, + }; + const observer = new PrometheusManagerTxObserver.createTxObserver(options, undefined, undefined, 'fakeUuid'); + observer.method.should.equal('periodic'); + sinon.assert.calledOnce(warnLogger); + }); + + it ('should use default collationCount and print error log when method is collate and collationCount is invalid', () => { + const options = { + method: 'collate', + collationCount: -1, + }; + const observer = new PrometheusManagerTxObserver.createTxObserver(options, undefined, undefined, 'fakeUuid'); + observer.method.should.equal('collate'); + expect(observer.collationCount).to.equal(10); + sinon.assert.calledOnce(errorLogger); + }); + + it ('should warn when interval is specified but method is collate', () => { + const options = { + method: 'collate', + interval: 1000, + }; + const observer = new PrometheusManagerTxObserver.createTxObserver(options, undefined, undefined, 'fakeUuid'); + observer.method.should.equal('collate'); + sinon.assert.calledOnce(warnLogger); + }); + + + it('should update the pending messages array when TXs are submitted', async () => { const senderUuid = 'senderUuid'; const messenger = { getUUID: sinon.stub().returns(senderUuid), - send: sinon.spy() + send: sinon.stub() }; const workerIndex = 0; const roundIndex = 1; @@ -109,25 +189,16 @@ describe('When using a PrometheusManagerTxObserver', () => { observer.currentRound = roundIndex; observer.roundLabel = roundLabel; - observer.txSubmitted(txCount); + await observer.txSubmitted(txCount); - sinon.assert.calledWith(messenger.send, sinon.match({ - content: sinon.match({ - event: 'txSubmitted', - roundIndex: roundIndex, - roundLabel: roundLabel, - count: txCount, - }), - sender: senderUuid, - recipients: sinon.match.array.contains([managerUuid]) - })); + observer.pendingMessages.should.have.lengthOf(1); }); - it('should send update message when single TX is finished', () => { + it('should update the pending messages array when single TX is finished', async () => { const senderUuid = 'senderUuid'; const messenger = { getUUID: sinon.stub().returns(senderUuid), - send: sinon.spy() + send: sinon.stub() }; const workerIndex = 0; const roundIndex = 1; @@ -147,26 +218,16 @@ describe('When using a PrometheusManagerTxObserver', () => { observer.currentRound = roundIndex; observer.roundLabel = roundLabel; - observer.txFinished(result); + await observer.txFinished(result); - sinon.assert.calledWith(messenger.send, sinon.match({ - content: sinon.match({ - event: 'txFinished', - roundIndex: roundIndex, - roundLabel: roundLabel, - status: 'success', - latency: (timeFinal - timeCreate) / 1000, - }), - sender: senderUuid, - recipients: sinon.match.array.contains([managerUuid]) - })); + observer.pendingMessages.should.have.lengthOf(1); }); - it('should send update message when multiple TXs are finished', () => { + it('should update the pending messages array when multiple TXs are finished', async () => { const senderUuid = 'senderUuid'; const messenger = { getUUID: sinon.stub().returns(senderUuid), - send: sinon.spy() + send: sinon.stub() }; const workerIndex = 0; const roundIndex = 1; @@ -186,29 +247,379 @@ describe('When using a PrometheusManagerTxObserver', () => { observer.currentRound = roundIndex; observer.roundLabel = roundLabel; - observer.txFinished([result, result]); - - sinon.assert.calledWith(messenger.send, sinon.match({ - content: sinon.match({ - event: 'txFinished', - roundIndex: roundIndex, - roundLabel: roundLabel, - status: 'success', - latency: (timeFinal - timeCreate), - }), - sender: senderUuid, - recipients: sinon.match.array.contains([managerUuid]) - })); - sinon.assert.calledWith(messenger.send, sinon.match({ - content: sinon.match({ - event: 'txFinished', - roundIndex: roundIndex, - roundLabel: roundLabel, - status: 'success', - latency: (timeFinal - timeCreate), - }), - sender: senderUuid, - recipients: sinon.match.array.contains([managerUuid]) - })); + await observer.txFinished([result, result]); + + observer.pendingMessages.should.have.lengthOf(2); + }); + + it('should trigger update when collationCount is crossed with the collate method', async () => { + const senderUuid = 'senderUuid'; + const messenger = { + getUUID: sinon.stub().returns(senderUuid), + send: sinon.stub() + }; + const workerIndex = 0; + const roundIndex = 1; + const roundLabel = 'roundLabel'; + const managerUuid = 'managerUuid'; + const timeFinal = 1000; + const timeCreate = 0; + + const result = { + GetStatus: sinon.stub().returns('success'), + GetTimeFinal: sinon.stub().returns(timeFinal), + GetTimeCreate: sinon.stub().returns(timeCreate), + }; + + const options = { + method: 'collate', + collationCount: 2, + }; + + const observer = new PrometheusManagerTxObserver.createTxObserver(options, messenger, workerIndex, managerUuid); + observer.messenger = messenger; + observer.currentRound = roundIndex; + observer.roundLabel = roundLabel; + + observer._sendUpdate = sinon.spy(); + + await observer.txFinished([result, result]); + + observer._sendUpdate.should.have.been.calledOnce; + }); + + it('should not trigger update until collation count is reached with method collate', async () => { + const senderUuid = 'senderUuid'; + const messenger = { + getUUID: sinon.stub().returns(senderUuid), + send: sinon.stub() + }; + const workerIndex = 0; + const roundIndex = 1; + const roundLabel = 'roundLabel'; + const managerUuid = 'managerUuid'; + const timeFinal = 1000; + const timeCreate = 0; + + const result = { + GetStatus: sinon.stub().returns('success'), + GetTimeFinal: sinon.stub().returns(timeFinal), + GetTimeCreate: sinon.stub().returns(timeCreate), + }; + + const options = { + method: 'collate', + collationCount: 2, + }; + + const observer = new PrometheusManagerTxObserver.createTxObserver(options, messenger, workerIndex, managerUuid); + observer.messenger = messenger; + observer.currentRound = roundIndex; + observer.roundLabel = roundLabel; + + observer._sendUpdate = sinon.spy(); + + await observer.txFinished(result); + + observer._sendUpdate.should.not.have.been.called; + }); + + it('should send pending messages when collation count is reached with method collate', async () => { + const senderUuid = 'senderUuid'; + const messenger = { + getUUID: sinon.stub().returns(senderUuid), + send: sinon.spy() + }; + const workerIndex = 0; + const roundIndex = 1; + const roundLabel = 'roundLabel'; + const managerUuid = 'managerUuid'; + const timeFinal = 1000; + const timeCreate = 0; + + const result = { + GetStatus: sinon.stub().returns('success'), + GetTimeFinal: sinon.stub().returns(timeFinal), + GetTimeCreate: sinon.stub().returns(timeCreate), + }; + + const options = { + method: 'collate', + collationCount: 2, + }; + + const observer = new PrometheusManagerTxObserver.createTxObserver(options, messenger, workerIndex, managerUuid); + observer.messenger = messenger; + observer.currentRound = roundIndex; + observer.roundLabel = roundLabel; + + await observer.txFinished([result, result]); + + messenger.send.should.have.been.calledTwice; + }); + + it('should clear pending messages when collation count is reached with method collate', async () => { + const senderUuid = 'senderUuid'; + const messenger = { + getUUID: sinon.stub().returns(senderUuid), + send: sinon.stub() + }; + const workerIndex = 0; + const roundIndex = 1; + const roundLabel = 'roundLabel'; + const managerUuid = 'managerUuid'; + const timeFinal = 1000; + const timeCreate = 0; + + const result = { + GetStatus: sinon.stub().returns('success'), + GetTimeFinal: sinon.stub().returns(timeFinal), + GetTimeCreate: sinon.stub().returns(timeCreate), + }; + + const options = { + method: 'collate', + collationCount: 2, + }; + + const observer = new PrometheusManagerTxObserver.createTxObserver(options, messenger, workerIndex, managerUuid); + observer.messenger = messenger; + observer.currentRound = roundIndex; + observer.roundLabel = roundLabel; + + await observer.txFinished([result, result]); + + observer.pendingMessages.should.have.lengthOf(0); + }); + + it('should setup interval timer with method periodic', async () => { + const senderUuid = 'senderUuid'; + const messenger = { + getUUID: sinon.stub().returns(senderUuid), + send: sinon.stub() + }; + const workerIndex = 0; + const roundIndex = 1; + const roundLabel = 'roundLabel'; + const managerUuid = 'managerUuid'; + + const options = { + method: 'periodic', + interval: 1000, + }; + + const observer = new PrometheusManagerTxObserver.createTxObserver(options, messenger, workerIndex, managerUuid); + observer.messenger = messenger; + observer.currentRound = roundIndex; + observer.roundLabel = roundLabel; + + await observer.activate(roundIndex, roundLabel); + + expect(observer.intervalObject).to.not.be.undefined; + }); + + it('should trigger update when interval is exceeded with method periodic', async () => { + const clock = sinon.useFakeTimers(); + + const senderUuid = 'senderUuid'; + const messenger = { + getUUID: sinon.stub().returns(senderUuid), + send: sinon.stub() + }; + const workerIndex = 0; + const roundIndex = 1; + const roundLabel = 'roundLabel'; + const managerUuid = 'managerUuid'; + const timeFinal = 1000; + const timeCreate = 0; + + const result = { + GetStatus: sinon.stub().returns('success'), + GetTimeFinal: sinon.stub().returns(timeFinal), + GetTimeCreate: sinon.stub().returns(timeCreate), + }; + + const options = { + method: 'periodic', + interval: 1000, + }; + + const observer = new PrometheusManagerTxObserver.createTxObserver(options, messenger, workerIndex, managerUuid); + observer.messenger = messenger; + observer.currentRound = roundIndex; + observer.roundLabel = roundLabel; + + observer._sendUpdate = sinon.spy(); + + await observer.activate(roundIndex, roundLabel); + await observer.txFinished(result); + + observer._sendUpdate.should.not.have.been.called; + + clock.tick(1000); + + observer._sendUpdate.should.have.been.calledOnce; + + clock.restore(); + }); + + it('should send pending messages when interval is exceeded with method periodic', async () => { + const clock = sinon.useFakeTimers(); + + const senderUuid = 'senderUuid'; + const messenger = { + getUUID: sinon.stub().returns(senderUuid), + send: sinon.spy() + }; + const workerIndex = 0; + const roundIndex = 1; + const roundLabel = 'roundLabel'; + const managerUuid = 'managerUuid'; + const timeFinal = 1000; + const timeCreate = 0; + + const result = { + GetStatus: sinon.stub().returns('success'), + GetTimeFinal: sinon.stub().returns(timeFinal), + GetTimeCreate: sinon.stub().returns(timeCreate), + }; + + const options = { + method: 'periodic', + interval: 1000, + }; + + const observer = new PrometheusManagerTxObserver.createTxObserver(options, messenger, workerIndex, managerUuid); + observer.messenger = messenger; + observer.currentRound = roundIndex; + observer.roundLabel = roundLabel; + + await observer.activate(roundIndex, roundLabel); + await observer.txFinished(result); + + messenger.send.should.not.have.been.called; + + clock.tick(1000); + + messenger.send.should.have.been.calledOnce; + + clock.restore(); + }); + + it('should clear pending messages when interval is exceeded with method periodic', async () => { + const clock = sinon.useFakeTimers(); + + const senderUuid = 'senderUuid'; + const messenger = { + getUUID: sinon.stub().returns(senderUuid), + send: sinon.stub() + }; + const workerIndex = 0; + const roundIndex = 1; + const roundLabel = 'roundLabel'; + const managerUuid = 'managerUuid'; + const timeFinal = 1000; + const timeCreate = 0; + + const result = { + GetStatus: sinon.stub().returns('success'), + GetTimeFinal: sinon.stub().returns(timeFinal), + GetTimeCreate: sinon.stub().returns(timeCreate), + }; + + const options = { + method: 'periodic', + interval: 1000, + }; + + const observer = new PrometheusManagerTxObserver.createTxObserver(options, messenger, workerIndex, managerUuid); + observer.messenger = messenger; + observer.currentRound = roundIndex; + observer.roundLabel = roundLabel; + + await observer.activate(roundIndex, roundLabel); + await observer.txFinished(result); + + observer.pendingMessages.should.have.lengthOf(1); + + clock.tick(1000); + + observer.pendingMessages.should.have.lengthOf(0); + + clock.restore(); + }); + + it('should clear interval timer when deactivated with method periodic', async () => { + const clock = sinon.useFakeTimers(); + + const senderUuid = 'senderUuid'; + const messenger = { + getUUID: sinon.stub().returns(senderUuid), + send: sinon.stub() + }; + const workerIndex = 0; + const roundIndex = 1; + const roundLabel = 'roundLabel'; + const managerUuid = 'managerUuid'; + + const options = { + method: 'periodic', + interval: 1000, + }; + + const observer = new PrometheusManagerTxObserver.createTxObserver(options, messenger, workerIndex, managerUuid); + observer.messenger = messenger; + observer.currentRound = roundIndex; + observer.roundLabel = roundLabel; + + await observer.activate(roundIndex, roundLabel); + await observer.deactivate(); + + expect(observer.intervalObject).to.be.undefined; + + clock.restore(); + }); + + it('should send pending messages when deactivated', async () => { + const clock = sinon.useFakeTimers(); + + const senderUuid = 'senderUuid'; + const messenger = { + getUUID: sinon.stub().returns(senderUuid), + send: sinon.spy() + }; + const workerIndex = 0; + const roundIndex = 1; + const roundLabel = 'roundLabel'; + const managerUuid = 'managerUuid'; + const timeFinal = 1000; + const timeCreate = 0; + + const result = { + GetStatus: sinon.stub().returns('success'), + GetTimeFinal: sinon.stub().returns(timeFinal), + GetTimeCreate: sinon.stub().returns(timeCreate), + }; + + const options = { + method: 'periodic', + interval: 1000, + }; + + const observer = new PrometheusManagerTxObserver.createTxObserver(options, messenger, workerIndex, managerUuid); + observer.messenger = messenger; + observer.currentRound = roundIndex; + observer.roundLabel = roundLabel; + + await observer.activate(roundIndex, roundLabel); + await observer.txFinished(result); + + messenger.send.should.not.have.been.called; + + await observer.deactivate(); + + messenger.send.should.have.been.calledOnce; + + clock.restore(); }); }); From 4fc70d1467e363f7d3e6459cb6cf7f393d5e5a7f Mon Sep 17 00:00:00 2001 From: CaptainIRS <36656347+CaptainIRS@users.noreply.github.com> Date: Mon, 17 Oct 2022 09:48:32 +0530 Subject: [PATCH 3/3] Address comments from Discord Co-authored-by: Dave Kelsey Signed-off-by: CaptainIRS <36656347+CaptainIRS@users.noreply.github.com> --- .../prometheus-manager-tx-observer.js | 29 ++++++-- .../prometheus-manager-tx-observer.js | 73 +++++++++++-------- 2 files changed, 62 insertions(+), 40 deletions(-) diff --git a/packages/caliper-core/lib/worker/tx-observers/prometheus-manager-tx-observer.js b/packages/caliper-core/lib/worker/tx-observers/prometheus-manager-tx-observer.js index 2fea7f3bd..fde97e903 100644 --- a/packages/caliper-core/lib/worker/tx-observers/prometheus-manager-tx-observer.js +++ b/packages/caliper-core/lib/worker/tx-observers/prometheus-manager-tx-observer.js @@ -36,29 +36,42 @@ class PrometheusManagerTxObserver extends TxObserverInterface { super(messenger, workerIndex); this.method = (options && options.method) ? options.method : ConfigUtil.get(ConfigUtil.keys.Observer.PrometheusManager.Method); - if (this.method === 'periodic') { + + switch (this.method) { + case 'periodic': { this.updateInterval = (options && options.interval) ? options.interval : ConfigUtil.get(ConfigUtil.keys.Observer.PrometheusManager.Interval); this.intervalObject = undefined; if (this.updateInterval <= 0) { - Logger.error('Invalid update interval specified, using default value'); this.updateInterval = ConfigUtil.get(ConfigUtil.keys.Observer.PrometheusManager.Interval); + Logger.warn(`Invalid update interval specified, using default value of ${this.updateInterval}`); } if (options && options.collationCount) { Logger.warn('Collation count is ignored when using periodic method'); } - } else if (this.method === 'collate') { + break; + } + + case 'collate' : { this.collationCount = (options && options.collationCount) ? options.collationCount : ConfigUtil.get(ConfigUtil.keys.Observer.PrometheusManager.CollationCount); if (this.collationCount <= 0) { - Logger.error('Invalid collation count specified, using default value'); this.collationCount = ConfigUtil.get(ConfigUtil.keys.Observer.PrometheusManager.CollationCount); + Logger.warn(`Invalid collation count specified, using default value of ${this.collationCount}`); } if (options && options.interval) { Logger.warn('Update interval is ignored when using collate method'); } + break; } - this.pendingMessages = []; + default: { + const msg = `Unrecognised method '${this.method}' specified for prometheus manager, must be either 'collate' or 'periodic' `; + Logger.error(msg); + throw new Error(msg); + } + + } + this.pendingMessages = []; this.managerUuid = managerUuid; } /** @@ -117,7 +130,7 @@ class PrometheusManagerTxObserver extends TxObserverInterface { this.pendingMessages.push(message); if (this.method === 'collate' && this.pendingMessages.length === this.collationCount) { - await this._sendUpdate(); + this._sendUpdate(); } } @@ -125,7 +138,7 @@ class PrometheusManagerTxObserver extends TxObserverInterface { * Sends the current aggregated statistics to the manager node when triggered by "setInterval". * @private */ - async _sendUpdate() { + _sendUpdate() { for (const message of this.pendingMessages) { this.messenger.send(message); } @@ -141,7 +154,7 @@ class PrometheusManagerTxObserver extends TxObserverInterface { await super.activate(roundIndex, roundLabel); if (this.method === 'periodic') { - this.intervalObject = setInterval(async () => { await this._sendUpdate(); }, this.updateInterval); + this.intervalObject = setInterval(async () => { this._sendUpdate(); }, this.updateInterval); } } diff --git a/packages/caliper-core/test/worker/tx-observers/prometheus-manager-tx-observer.js b/packages/caliper-core/test/worker/tx-observers/prometheus-manager-tx-observer.js index 8645b3d2d..14efd586c 100644 --- a/packages/caliper-core/test/worker/tx-observers/prometheus-manager-tx-observer.js +++ b/packages/caliper-core/test/worker/tx-observers/prometheus-manager-tx-observer.js @@ -97,77 +97,86 @@ describe('When using a PrometheusManagerTxObserver', () => { it('should set managerUuid passed through constructor', () => { const observer = new PrometheusManagerTxObserver.createTxObserver(undefined, undefined, undefined, 'fakeUuid'); - observer.managerUuid.should.equal('fakeUuid'); + expect(observer.managerUuid).to.equal('fakeUuid'); }); - it ('should set the correct parameters when method is periodic', () => { + it('should set the correct parameters when method is periodic', () => { const options = { method: 'periodic', interval: 1000, }; const observer = new PrometheusManagerTxObserver.createTxObserver(options, undefined, undefined, 'fakeUuid'); - observer.method.should.equal('periodic'); + expect(observer.method).to.equal('periodic'); expect(observer.updateInterval).to.equal(1000); expect(observer.intervalObject).to.equal(undefined); }); - it ('should set the correct parameters when method is collate', () => { + it('should set the correct parameters when method is collate', () => { const options = { method: 'collate', collationCount: 10, }; const observer = new PrometheusManagerTxObserver.createTxObserver(options, undefined, undefined, 'fakeUuid'); - observer.method.should.equal('collate'); + expect(observer.method).to.equal('collate'); expect(observer.collationCount).to.equal(10); }); - it ('should set the default method when options are not provided', () => { + it('should set the default method when options are not provided', () => { const observer = new PrometheusManagerTxObserver.createTxObserver(undefined, undefined, undefined, 'fakeUuid'); - observer.method.should.equal('periodic'); + expect(observer.method).to.equal('periodic'); expect(observer.updateInterval).to.equal(1000); expect(observer.intervalObject).to.equal(undefined); }); - it ('should use default update interval and print error log when method is periodic and interval is invalid', () => { + it('should throw an error if an unknown method is specified', () => { + const options = { + method: 'profjgd' + }; + expect(() => { + new PrometheusManagerTxObserver.createTxObserver(options, undefined, undefined, 'fakeUuid'); + }).to.throw(/Unrecognised method 'profjgd' specified for prometheus manager, must be either 'collate' or 'periodic'/); + }); + + it('should use default update interval and print warning when method is periodic and interval is invalid', () => { const options = { method: 'periodic', interval: -1, }; const observer = new PrometheusManagerTxObserver.createTxObserver(options, undefined, undefined, 'fakeUuid'); - observer.method.should.equal('periodic'); + expect(observer.method).to.equal('periodic'); expect(observer.updateInterval).to.equal(1000); expect(observer.intervalObject).to.equal(undefined); - sinon.assert.calledOnce(errorLogger); + sinon.assert.calledOnce(warnLogger); }); - it ('should warn when collationCount is specified but method is periodic', () => { + it('should warn when collationCount is specified but method is periodic', () => { const options = { method: 'periodic', collationCount: 10, }; const observer = new PrometheusManagerTxObserver.createTxObserver(options, undefined, undefined, 'fakeUuid'); - observer.method.should.equal('periodic'); + expect(observer.method).to.equal('periodic'); sinon.assert.calledOnce(warnLogger); }); - it ('should use default collationCount and print error log when method is collate and collationCount is invalid', () => { + it('should use default collationCount and print warning when method is collate and collationCount is invalid', () => { const options = { method: 'collate', collationCount: -1, }; const observer = new PrometheusManagerTxObserver.createTxObserver(options, undefined, undefined, 'fakeUuid'); - observer.method.should.equal('collate'); + expect(observer.method).to.equal('collate'); expect(observer.collationCount).to.equal(10); - sinon.assert.calledOnce(errorLogger); + sinon.assert.calledOnce(warnLogger); }); - it ('should warn when interval is specified but method is collate', () => { + it('should warn when interval is specified but method is collate', () => { const options = { method: 'collate', interval: 1000, }; const observer = new PrometheusManagerTxObserver.createTxObserver(options, undefined, undefined, 'fakeUuid'); - observer.method.should.equal('collate'); + expect(observer.method).to.equal('collate'); sinon.assert.calledOnce(warnLogger); }); @@ -191,7 +200,7 @@ describe('When using a PrometheusManagerTxObserver', () => { await observer.txSubmitted(txCount); - observer.pendingMessages.should.have.lengthOf(1); + expect(observer.pendingMessages).to.have.lengthOf(1); }); it('should update the pending messages array when single TX is finished', async () => { @@ -220,7 +229,7 @@ describe('When using a PrometheusManagerTxObserver', () => { await observer.txFinished(result); - observer.pendingMessages.should.have.lengthOf(1); + expect(observer.pendingMessages).to.have.lengthOf(1); }); it('should update the pending messages array when multiple TXs are finished', async () => { @@ -249,7 +258,7 @@ describe('When using a PrometheusManagerTxObserver', () => { await observer.txFinished([result, result]); - observer.pendingMessages.should.have.lengthOf(2); + expect(observer.pendingMessages).to.have.lengthOf(2); }); it('should trigger update when collationCount is crossed with the collate method', async () => { @@ -285,7 +294,7 @@ describe('When using a PrometheusManagerTxObserver', () => { await observer.txFinished([result, result]); - observer._sendUpdate.should.have.been.calledOnce; + expect(observer._sendUpdate).to.have.been.calledOnce; }); it('should not trigger update until collation count is reached with method collate', async () => { @@ -321,7 +330,7 @@ describe('When using a PrometheusManagerTxObserver', () => { await observer.txFinished(result); - observer._sendUpdate.should.not.have.been.called; + expect(observer._sendUpdate).to.not.have.been.called; }); it('should send pending messages when collation count is reached with method collate', async () => { @@ -355,7 +364,7 @@ describe('When using a PrometheusManagerTxObserver', () => { await observer.txFinished([result, result]); - messenger.send.should.have.been.calledTwice; + expect(messenger.send).to.have.been.calledTwice; }); it('should clear pending messages when collation count is reached with method collate', async () => { @@ -389,7 +398,7 @@ describe('When using a PrometheusManagerTxObserver', () => { await observer.txFinished([result, result]); - observer.pendingMessages.should.have.lengthOf(0); + expect(observer.pendingMessages).to.have.lengthOf(0); }); it('should setup interval timer with method periodic', async () => { @@ -454,11 +463,11 @@ describe('When using a PrometheusManagerTxObserver', () => { await observer.activate(roundIndex, roundLabel); await observer.txFinished(result); - observer._sendUpdate.should.not.have.been.called; + expect(observer._sendUpdate).to.not.have.been.called; clock.tick(1000); - observer._sendUpdate.should.have.been.calledOnce; + expect(observer._sendUpdate).to.have.been.calledOnce; clock.restore(); }); @@ -497,11 +506,11 @@ describe('When using a PrometheusManagerTxObserver', () => { await observer.activate(roundIndex, roundLabel); await observer.txFinished(result); - messenger.send.should.not.have.been.called; + expect(messenger.send).to.not.have.been.called; clock.tick(1000); - messenger.send.should.have.been.calledOnce; + expect(messenger.send).to.have.been.calledOnce; clock.restore(); }); @@ -540,11 +549,11 @@ describe('When using a PrometheusManagerTxObserver', () => { await observer.activate(roundIndex, roundLabel); await observer.txFinished(result); - observer.pendingMessages.should.have.lengthOf(1); + expect(observer.pendingMessages).to.have.lengthOf(1); clock.tick(1000); - observer.pendingMessages.should.have.lengthOf(0); + expect(observer.pendingMessages).to.have.lengthOf(0); clock.restore(); }); @@ -614,11 +623,11 @@ describe('When using a PrometheusManagerTxObserver', () => { await observer.activate(roundIndex, roundLabel); await observer.txFinished(result); - messenger.send.should.not.have.been.called; + expect(messenger.send).to.not.have.been.called; await observer.deactivate(); - messenger.send.should.have.been.calledOnce; + expect(messenger.send).to.have.been.calledOnce; clock.restore(); });