Skip to content

Instantly share code, notes, and snippets.

@nathanpeck
Created May 6, 2021 03:59
Show Gist options
  • Save nathanpeck/54ad0aee1638b5b57569ac6e38d81949 to your computer and use it in GitHub Desktop.
Save nathanpeck/54ad0aee1638b5b57569ac6e38d81949 to your computer and use it in GitHub Desktop.
var request = require('request');
var async = require('async');
var faker = require('faker');
var _ = require('lodash');
if (!process.env.PEAK_PERIOD_MINS) {
process.env.PEAK_PERIOD_MINS = '30';
}
var PEAK_PERIOD_MINS = parseInt(process.env.PEAK_PERIOD_MINS, 10) * 60 * 1000;
var MAX_CONCURRENT = parseInt(process.env.MAX_CONCURRENT, 10) || 100;
var MIN_CONCURRENT = parseInt(process.env.MIN_CONCURRENT, 10) || 0;
var EXTERNAL_URL = process.env.EXTERNAL_URL;
console.log(`Running forever, peaking every ${process.env.PEAK_PERIOD_MINS} minutes starting from ` +
`${MIN_CONCURRENT} concurrent agents, and peaking at ${MAX_CONCURRENT} ` +
'concurrent agents');
var client = request.defaults({
baseUrl: EXTERNAL_URL,
json: true
});
function scaleBetween(unscaledNum, minAllowed, maxAllowed, min, max) {
return (maxAllowed - minAllowed) * (unscaledNum - min) / (max - min) + minAllowed;
}
function randomId() {
return (new Date().getTime() / 1000 | 0).toString(16) + 'xxxxxxxxxxxxxxxx'.replace(/[x]/g, function() {
return (Math.random() * 16 | 0).toString(16);
}).toLowerCase();
}
// The core behavior of the agent once it has finished setting up.
function agentLoop(auth) {
return function(number, done) {
async.auto({
hashtag: function(next) {
// Check the #ijustsignedup hashtag to find other people follow
client({
method: 'GET',
uri: '/api/timeline/hashtag/ijustsignedup',
}, function(err, resp, body) {
if (err) {
console.error('Networking error', err);
return next('fail');
}
if (resp.statusCode !== 200) {
console.error('>>>>> Got unexpected status code from timeline service: ' + resp.statusCode, body);
return next('fail');
}
return next(null, body);
});
},
followSomeone: ['hashtag', function(context, next) {
// 1 in N chance per loop to follow someone new.
if (_.random(6) !== 6) {
return next();
}
var toFollow = _.sample(Object.keys(context.hashtag.users));
client({
method: 'POST',
uri: `/api/subscription/users/${toFollow}/subscribers/me`,
auth: {
bearer: auth.token
}
}, function(err, resp, body) {
if (err) {
console.error('Networking error', err);
return next('fail');
}
if (resp.statusCode !== 200) {
console.error('>>>>> Got unexpected status code from subscription service: ' + resp.statusCode, body);
return next('fail');
}
return next();
});
}],
sendMessage: function(next) {
client({
method: 'POST',
uri: '/api/message',
auth: {
bearer: auth.token
},
body: {
payload: {
text: 'load test message ' + number
}
}
}, function(err, resp, body) {
if (err) {
console.error('Networking error', err);
return next('fail');
}
if (resp.statusCode !== 200) {
console.error('Sending message got status code: ' + resp.statusCode, body);
return next('fail');
}
setTimeout(next, 1000);
});
},
checkTimeline: ['sendMessage', function(context, next) {
// Check messages
client({
method: 'GET',
uri: `/api/timeline/users/${auth.id}/to`,
}, function(err, resp, body) {
if (err) {
console.error('Networking error', err);
return next('fail');
}
if (resp.statusCode !== 200) {
console.error('>>>>> Got unexpected status code from timeline service: ' + resp.statusCode, body);
return next('fail');
}
return next(null, body);
});
}],
checkOwnMessages: ['sendMessage', function(context, next) {
// Check messages
client({
method: 'GET',
uri: `/api/timeline/users/${auth.id}/by`,
}, function(err, resp, body) {
if (err) {
console.error('Networking error', err);
return next('fail');
}
if (resp.statusCode !== 200) {
console.error('>>>>> Got unexpected status code from timeline service: ' + resp.statusCode, body);
return next('fail');
}
return next(null, body);
});
}]
}, function() {
setTimeout(done, 4000);
});
};
}
function agent(done) {
var firstName = faker.name.firstName();
var lastName = faker.name.lastName();
var password = randomId();
var auth;
var name = firstName + ' ' + lastName;
var username = firstName + '_' + lastName;
username = username.toLowerCase() + '_' + Date.now();
var avatar = faker.image.avatar();
async.auto({
website: function(next) {
client.get('/', function() {
setTimeout(next, 1000);
});
},
createAccount: ['website', function(context, next) {
client({
method: 'POST',
uri: '/api/user',
body: {
username: username,
name: name,
password: password,
avatar: avatar
}
}, function(err, resp, body) {
console.log(`welcome ${name}, aka @${username} - (${concurrent} concurrent agents)`);
if (err) {
console.error('Networking error', err);
return next('fail');
}
if (resp.statusCode !== 200) {
console.error('Hitting website got status code: ' + resp.statusCode, body);
return next('fail');
}
setTimeout(next, 2000);
});
}],
authenticate: ['createAccount', function(context, next) {
client({
method: 'POST',
uri: '/api/auth/challenge',
body: {
username: username,
password: password
}
}, function(err, resp, body) {
if (err) {
console.error('Networking error', err);
return next('fail');
}
if (resp.statusCode !== 200) {
console.error('Creating account got status code: ' + resp.statusCode, body);
return next('fail');
}
auth = body;
setTimeout(next, 2000);
});
}],
selfDetails: ['authenticate', function(context, next) {
client({
method: 'GET',
uri: '/api/user/users/me',
auth: {
bearer: auth.token
}
}, function(err, resp, body) {
if (err) {
console.error('Networking error', err);
return next('fail');
}
if (resp.statusCode !== 200) {
console.error('Authenticating got status code: ' + resp.statusCode, body);
return next('fail');
}
next();
});
}],
// Send a message to the #ijustsignedup hashtag so that other
// bots can find this bot to follow it.
broadcastSignup: ['selfDetails', function(context, next) {
client({
method: 'POST',
uri: '/api/message',
auth: {
bearer: auth.token
},
body: {
payload: {
text: 'hello world! I am ' + name + ' and #ijustsignedup'
}
}
}, function(err, resp, body) {
if (err) {
console.error('Networking error', err);
return next('fail');
}
if (resp.statusCode !== 200) {
console.error('Sending message got status code: ' + resp.statusCode, body);
return next('fail');
}
next();
});
}],
mainLoop: ['broadcastSignup', function(context, next) {
async.timesSeries(30, agentLoop(auth), next);
}],
}, done);
}
var concurrent = 0;
function maybeLaunchAgent() {
var pointInTime = Date.now() % PEAK_PERIOD_MINS * 2;
if (pointInTime > PEAK_PERIOD_MINS) {
// We are in the downslope period
pointInTime = (PEAK_PERIOD_MINS * 2) - pointInTime;
}
var targetConcurrent = scaleBetween(pointInTime, MAX_CONCURRENT, MIN_CONCURRENT, PEAK_PERIOD_MINS, 0);
if (concurrent < targetConcurrent) {
concurrent++;
// Launch another agent.
agent(function() {
concurrent--;
});
}
setTimeout(maybeLaunchAgent, 1000); // Launch agents at a rate of once per second
}
maybeLaunchAgent();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment