Skip to content

Commit

Permalink
add more test for fixedFeedbackRate controller implementation (#1628)
Browse files Browse the repository at this point in the history
Signed-off-by: Babatunde Sanusi <[email protected]>
  • Loading branch information
tunedev authored Nov 12, 2024
1 parent ade355f commit 5617b01
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ class FixedFeedbackRateController extends RateInterface {
if (this.generalSleepTime === 0 || currentSubmitted < this.unfinishedPerWorker) {
return;
}

if (this.stats.getTotalFinishedTx() === 0) {
return;
}
Expand All @@ -74,7 +73,12 @@ class FixedFeedbackRateController extends RateInterface {
return;
}

let diff = (this.generalSleepTime * currentSubmitted - ((Date.now() - this.totalSleepTime) - this.stats.getRoundStartTime()));
const expectedTime = this.generalSleepTime * currentSubmitted;
const actualElapsedTime = (Date.now() - this.totalSleepTime) - this.stats.getRoundStartTime();
const diff = expectedTime - actualElapsedTime;

// If we're ahead by more than 5ms, adjust by sleeping for the difference
// 5ms is used to avoid negligible adjustments that could cause unnecessary delays
if (diff > 5) {
await util.sleep(diff);
return;
Expand Down
159 changes: 147 additions & 12 deletions packages/caliper-core/test/worker/rate-control/fixedFeedbackRate.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ describe('fixedFeedbackRate controller implementation', () => {
});

describe('#applyRateController', () => {
let controller, sleepStub, txnStats, clock;
let controller, sleepStub, txnStats, clock, testMessage;

beforeEach( () => {
const msgContent = {
Expand All @@ -99,7 +99,7 @@ describe('fixedFeedbackRate controller implementation', () => {
sleepStub = sinon.stub();
FixedFeedbackRate.__set__('util.sleep', sleepStub);

const testMessage = new TestMessage('test', [], msgContent);
testMessage = new TestMessage('test', [], msgContent);
txnStats = new TransactionStatisticsCollector();
controller = new FixedFeedbackRate.createRateController(testMessage, txnStats, 0);
});
Expand All @@ -108,41 +108,176 @@ describe('fixedFeedbackRate controller implementation', () => {
clock.restore();
});

it('should not sleep if the generalSleepTime is 0', () => {
it('should not sleep if the generalSleepTime is 0', async () => {
controller.generalSleepTime = 0;
txnStats.stats.txCounters.totalSubmitted = 2000;
controller.applyRateControl();
await controller.applyRateControl();

// should have not called the sleep method
sinon.assert.notCalled(sleepStub);
});

it('should not sleep if there are no unfinished transactions', () => {
txnStats.stats.txCounters.totalSubmitted = 0;
controller.applyRateControl();
it('should not sleep if there are no unfinished transactions', async () => {
// Stub methods if necessary
sinon.stub(txnStats, 'getTotalSubmittedTx').returns(10);
sinon.stub(txnStats, 'getTotalFinishedTx').returns(0);

// should have not called the sleep method
await controller.applyRateControl();

// Ensure sleep was not called
sinon.assert.notCalled(sleepStub);
});

it('should not sleep if backlog transaction is below half the target', () => {
it('should not sleep if backlog transaction is below half the target', async () => {
txnStats.stats.txCounters.totalSubmitted = 1000;
txnStats.stats.txCounters.totalFinished = 999;
controller.generalSleepTime = 1;
controller.applyRateControl();
await controller.applyRateControl();

// should have not called the sleep method
sinon.assert.notCalled(sleepStub);
});

it ('should sleep if the elapsed time difference is greater than 5ms', () => {
it ('should sleep if the elapsed time difference is greater than 5ms', async () => {
txnStats.stats.txCounters.totalSubmitted = 100;
txnStats.stats.txCounters.totalFinished = 2;
controller.applyRateControl();
await controller.applyRateControl();

// should have called the sleep method with a value equal to diff
sinon.assert.calledOnce(sleepStub);
sinon.assert.calledWith(sleepStub, 20000);
});

it('should sleep for backOffTime when no successful transactions have occurred once', async () => {
txnStats.stats.txCounters.totalSubmitted = 5;
txnStats.stats.txCounters.totalFinished = 2;
txnStats.stats.txCounters.totalSuccessful = 0;
controller.generalSleepTime = 1;

await controller.applyRateControl();

sinon.assert.calledOnce(sleepStub);
sinon.assert.calledWith(sleepStub, 1 * controller.backOffTime);
});

it('should increase sleep time when no successful transactions have occurred multiple times', async () => {
// Configure the rate controller with a high TPS value
testMessage.content.rateControl.opts = {
tps: 10000,
sleepTime: 100,
transactionLoad: 10
};

controller = new FixedFeedbackRate.createRateController(testMessage, txnStats, 0);
txnStats.stats.txCounters.totalSubmitted = 5;
txnStats.stats.txCounters.totalFinished = 2;
txnStats.stats.txCounters.totalSuccessful = 0;

controller.zeroSuccessfulCounter = 3;
clock.tick(0); // Ensure clock starts at 0

await controller.applyRateControl();

sinon.assert.calledOnce(sleepStub);
// Expected sleep time should be 4 * backOffTime = 400ms
sinon.assert.calledWith(sleepStub, 4 * controller.backOffTime);
});

it('should cap the sleep time increase when zeroSuccessfulCounter exceeds threshold', async () => {
// Configure the rate controller with a high TPS value
testMessage.content.rateControl.opts = {
tps: 10000,
sleepTime: 100,
transactionLoad: 10
};

// Re-instantiate the controller with the new configuration
controller = new FixedFeedbackRate.createRateController(testMessage, txnStats, 0);

// Simulate conditions where there are no successful transactions
txnStats.stats.txCounters.totalSubmitted = 5;
txnStats.stats.txCounters.totalFinished = 2;
txnStats.stats.txCounters.totalSuccessful = 0;

// Set zeroSuccessfulCounter to 31
controller.zeroSuccessfulCounter = 31;

// Use the fake timers to control Date.now()
clock.tick(0); // Ensure clock starts at 0

await controller.applyRateControl();

sinon.assert.calledOnce(sleepStub);
// Expected sleep time should be capped at 30 * backOffTime = 3000ms
sinon.assert.calledWith(sleepStub, 30 * controller.backOffTime);
});

it('should sleep to adjust rate when expected time exceeds actual elapsed time', async () => {
txnStats.stats.txCounters.totalSubmitted = 44;
txnStats.stats.txCounters.totalFinished = 40;

// Simulate time progression
const elapsedTime = 1000; // e.g., 1000ms since start
clock.tick(elapsedTime);

await controller.applyRateControl();

sinon.assert.calledOnce(sleepStub);
// Calculate expected adjustment time
const expectedTime = controller.generalSleepTime * txnStats.getTotalSubmittedTx();
const actualElapsedTime = (Date.now() - controller.totalSleepTime) - txnStats.getRoundStartTime();
const adjustmentTime = expectedTime - actualElapsedTime;

sinon.assert.calledWith(sleepStub, adjustmentTime);
});

it('should sleep correctly based on the number of unfinished transactions', async () => {
// Configure the rate controller
testMessage.content.rateControl.opts = {
tps: 10000,
sleepTime: 100,
transactionLoad: 10
};

for (let i = 10; i > 0; --i) {
// Re-instantiate the controller for each iteration
controller = new FixedFeedbackRate.createRateController(testMessage, txnStats, 0);
const unfinishedPerWorker = controller.unfinishedPerWorker; // Should be 5

// Reset sleep stub
sleepStub.resetHistory();

// Ensure there are successful transactions
sinon.stub(txnStats, 'getTotalSuccessfulTx').returns(5);

// Stub getRoundStartTime
sinon.stub(txnStats, 'getRoundStartTime').returns(0);

// Stub getTotalSubmittedTx and getTotalFinishedTx
const unfinished = i * unfinishedPerWorker;
const totalFinished = 10;
const totalSubmitted = totalFinished + unfinished;

sinon.stub(txnStats, 'getTotalSubmittedTx').returns(totalSubmitted);
sinon.stub(txnStats, 'getTotalFinishedTx').returns(totalFinished);

// Control time
clock.reset();
clock.tick(12);

await controller.applyRateControl();

const expectedSleepTime = i * controller.backOffTime;

sinon.assert.calledOnce(sleepStub);
sinon.assert.calledWith(sleepStub, expectedSleepTime);

// Restore stubs
txnStats.getTotalSubmittedTx.restore();
txnStats.getTotalFinishedTx.restore();
txnStats.getTotalSuccessfulTx.restore();
txnStats.getRoundStartTime.restore();
}
});
});
});

0 comments on commit 5617b01

Please sign in to comment.