Last active
August 15, 2019 07:36
-
-
Save skubakdj/668bbc9f186213f17026163567566071 to your computer and use it in GitHub Desktop.
Stress test OpenMonero by starting a bunch of threads.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
steps to run (fill out the 'BASIC CONFIG' first): | |
mkdir stress_test && cd stress_test | |
npm init -y | |
npm install typescript ts-node axios git://github.com/mymonero/mymonero-core-js.git#1.0.0 | |
[download gist as threads.ts] | |
node_modules/.bin/ts-node threads.ts | |
*/ | |
import axios from 'axios' | |
const walletUtils = require('mymonero-core-js/monero_utils/monero_wallet_utils') | |
/** | |
* BASIC CONFIG | |
*/ | |
const LIGHTWALLET_ENDPOINT = 'http://192.168.1.1:1984/' | |
const TARGET_THREAD_COUNT = 5000 | |
const NETWORK: 'mainnet' | 'stagenet' = 'mainnet' | |
const THREAD_TEST_DURATION_MS = 1000 * 60 * 60 // one hour | |
/** | |
* ADDITIONAL CONFIG | |
*/ | |
const ADDRESS_LOOP_TIMEOUT_MS = 60000 | |
const HEARTBEAT_MS = 5000 | |
/** | |
* VARS | |
*/ | |
const startTime = new Date() | |
const http = axios.create({ | |
baseURL: LIGHTWALLET_ENDPOINT, | |
timeout: 40000, | |
headers: { | |
'Content-Type': 'application/json', | |
Accept: 'application/json', | |
}, | |
}) | |
const moneroKeys = generateKeyObject(TARGET_THREAD_COUNT, NETWORK) | |
let allKeysStarted = false | |
let allKeysStartedTime: Date | |
let globalAddressState: GlobalAddressState = {} | |
let targetEndTime: Date | |
let finishing = false | |
/** | |
* TYPES | |
*/ | |
type AddressInfoRequest = { | |
sentTime: Date | |
recievedTime: Date | |
responseTimeMs: number | |
response: any | |
} | |
type AddressState = { | |
loginReq: any | |
address: string | |
view_key: string | |
addressInfoRequests: AddressInfoRequest[] | |
} | |
type GlobalAddressState = { | |
[key: string]: AddressState | |
} | |
type KeyObject = { | |
[key: string]: { | |
address: string | |
view_key: string | |
} | |
} | |
async function main() { | |
const keys = Object.keys(moneroKeys) | |
for (const key of keys) { | |
try { | |
if (finishing) return | |
const { address, view_key } = moneroKeys[key] | |
const loginReq = await makeLoginRequest(address, view_key) | |
addressLoop(loginReq, key, address, view_key) | |
console.log(`Started key ${key}`) | |
} catch (err) { | |
console.log('Error caught in startImportStressTest:', err) | |
} | |
} | |
allKeysStarted = true | |
allKeysStartedTime = new Date() | |
targetEndTime = new Date( | |
allKeysStartedTime.getTime() + THREAD_TEST_DURATION_MS, | |
) | |
heartbeat() | |
} | |
const addressLoop = async ( | |
loginReq: any, | |
key: string, | |
address: string, | |
view_key: string, | |
) => { | |
let addressState: AddressState = { | |
loginReq, | |
address, | |
view_key, | |
addressInfoRequests: [], | |
} | |
let working = true | |
globalAddressState[key] = addressState | |
await sleep(5000) | |
while (working && !finishing) { | |
try { | |
const sentTime = new Date() | |
const response = await makeAddressInfoRequest(address, view_key) | |
const recievedTime = new Date() | |
const responseTimeMs = recievedTime.getTime() - sentTime.getTime() | |
addressState.addressInfoRequests.push({ | |
sentTime, | |
recievedTime, | |
responseTimeMs, | |
response, | |
}) | |
globalAddressState[key] = addressState | |
} catch (err) { | |
console.log(`makeAddressInfoRequest failed for key ${key}`) | |
} finally { | |
await sleep(ADDRESS_LOOP_TIMEOUT_MS) | |
} | |
} | |
} | |
async function heartbeat() { | |
let working = true | |
console.log('heartbeat started') | |
while (working && !finishing) { | |
const now = new Date() | |
await sleep(HEARTBEAT_MS) | |
if (!allKeysStarted) continue | |
if (now >= targetEndTime) { | |
console.log('target end time reached') | |
finished() | |
working = false | |
} else { | |
logStats() | |
} | |
} | |
} | |
const logStats = () => { | |
// average response time | |
const keys = Object.keys(globalAddressState) | |
const responseTimes = [] | |
for (const key of keys) { | |
const addrState = globalAddressState[key] | |
const responseCount = addrState.addressInfoRequests.length | |
if (!responseCount) continue | |
for (const info of addrState.addressInfoRequests) { | |
responseTimes.push(info.responseTimeMs) | |
} | |
} | |
const averageResponseTime = Math.floor( | |
sumArr(responseTimes) / responseTimes.length, | |
) | |
const now = new Date() | |
const msToFinish = targetEndTime.getTime() - now.getTime() | |
const timeToFinish = timeConversion(msToFinish) | |
console.log( | |
`Avg resp time: ${averageResponseTime} ms | Remaining: ${timeToFinish}`, | |
) | |
} | |
const finished = async () => { | |
const endTime = new Date() | |
finishing = true | |
console.log( | |
`Total time: ${timeConversion(endTime.getTime() - startTime.getTime())}`, | |
) | |
console.log('finished! exiting...') | |
process.exit() | |
} | |
/** | |
* HELPERS | |
*/ | |
const makeLoginRequest = async (address: string, view_key: string) => { | |
const { data } = await http.post('login', { | |
address, | |
view_key, | |
create_account: true, | |
generated_locally: true, | |
}) | |
return data | |
} | |
const makeAddressInfoRequest = async (address: string, view_key: string) => { | |
const { data } = await http.post('get_address_info', { | |
address, | |
view_key, | |
}) | |
return data | |
} | |
const sumArr = (arr: number[]) => arr.reduce((a, b) => a + b, 0) | |
const sleep = (time: number) => | |
new Promise(resolve => | |
setTimeout(() => { | |
resolve() | |
}, time), | |
) | |
// inspired by Nofi: | |
// https://stackoverflow.com/questions/19700283/how-to-convert-time-milliseconds-to-hours-min-sec-format-in-javascript/19700358 | |
function timeConversion(millisec: number) { | |
const seconds: number = millisec / 1000 | |
const minutes = millisec / (1000 * 60) | |
const hours = millisec / (1000 * 60 * 60) | |
const days = millisec / (1000 * 60 * 60 * 24) | |
if (seconds < 60) { | |
return seconds.toFixed(1) + ' Sec' | |
} else if (minutes < 60) { | |
return minutes.toFixed(1) + ' Min' | |
} else if (hours < 24) { | |
return hours.toFixed(1) + ' Hrs' | |
} else { | |
return days.toFixed(1) + ' Days' | |
} | |
} | |
function generateKeyObject( | |
total: number, | |
network: 'stagenet' | 'mainnet', | |
): KeyObject { | |
const networkNumber = network === 'mainnet' ? 0 : 2 | |
let count = 0 | |
let ret: KeyObject = {} | |
while (count < total) { | |
ret[`${count}`] = createKeys(networkNumber) | |
count++ | |
} | |
return ret | |
} | |
function createKeys( | |
network: number = 2, | |
): { address: string; view_key: string } { | |
const wallet = walletUtils.NewlyCreatedWallet('english', network) // 2 == stagenet | |
return { | |
address: wallet.keys.public_addr, | |
view_key: wallet.keys.view.pub, | |
} | |
} | |
// call so we can handle SIGINT | |
process.stdin.resume() | |
process.on('SIGINT', async () => { | |
console.log('ctrl + c event detected') | |
await finished() | |
console.log('finished. exiting...') | |
process.exit() | |
}) | |
if (require.main === module) { | |
main() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
npm i @types/node --save-dev
was also needed to run it.