Back to Ccxt

Benchmark

wiki/examples/js/benchmark.md

4.5.525.1 KB
Original Source
javascript
import { spawn } from 'child_process';
import asTable from 'as-table';
import ccxt, { version } from '../../js/ccxt.js';
const stats = (times) => {
   // calculate statistics
   const sum = times.reduce((a, b) => a + b, 0);
   const avg = Math.round(sum / times.length);
   const min = Math.min(...times);
   const max = Math.max(...times);
   times.sort((a, b) => a - b);
   const median = times.length % 2 === 0 ? (times[times.length / 2 - 1] + times[times.length / 2]) / 2 : times[Math.floor(times.length / 2)];
   return { min, 'average': avg, max, median, 'iterations': times.length };
};
async function benchmark(exchangeId, method, args, verbose = false, minIterations = 10, argsv = '') {
   const exchange = new ccxt.pro[exchangeId]({});
   const languages = ['js', 'py', 'php', 'cs'];
   const commands = languages.map((language) => ({
       'language': language,
       'method': method,
       'command': `npm run cli.${language} ${exchangeId} ${method} ${args.join(' ')} -- ${argsv} --poll`,
   }));
   const wsMethod = method + 'Ws';
   if (exchange.has[wsMethod]) {
       const wsCommands = languages.map((language) => ({
           'language': language,
           'method': wsMethod,
           'command': `npm run cli.${language} ${exchangeId} ${wsMethod} ${args.join(' ')} -- ${argsv} --poll`,
       }));
       commands.push(...wsCommands);
   }
   const regex = /iteration (\d+) passed in (\d+) ms/g;
   async function runCommand(command) {
       if (verbose) {
           console.log(exchange.iso8601(new Date().getTime()), ' running command:', command);
       }
       return new Promise((resolve, reject) => {
           const [cmd, ...args] = command.split(' ');
           const child = spawn(cmd, args);
           const matches = [];
           const language = command.slice(8, 15);
           child.stdout.on('data', (data) => {
               const message = data.toString();
               matches.push(...Array.from(message.matchAll(regex)));
               const match = matches[matches.length - 1];
               if (match && match[1] && match[2]) {
                   const iteration = parseInt(match[1]);
                   const time = parseInt(match[2]);
                   if (iteration <= minIterations) {
                       if (verbose) {
                           console.log(exchange.iso8601(new Date().getTime()), `${language} iteration ${iteration} passed in ${time} ms`);
                       }
                   }
                   else {
                       const times = matches.map((m) => parseInt(m[2]));
                       child.kill();
                       if (verbose) {
                           console.log(exchange.iso8601(new Date().getTime()), `killed process - ${language} iteration ${iteration} passed in ${time} ms`);
                       }
                       resolve({ times });
                   }
               }
           });
           child.stderr.on('data', (data) => {
               const message = data.toString();
               console.error(exchange.iso8601(new Date().getTime()), `command ${command} failed. stderr: ${message}`);
               const times = matches.map((m) => parseInt(m[2]));
               resolve({ times });
           });
           child.on('close', (code) => {
               const times = matches.map((m) => parseInt(m[2]));
               resolve({ times });
               console.log(exchange.iso8601(new Date().getTime()), `${language} child process exited with code ${code}`);
           });
           child.on('error', (err) => {
               console.error(exchange.iso8601(new Date().getTime()), `command ${command} failed. error: ${err}`);
               reject(err);
           });
       });
   }
   const benchmarks = [];
   const results = await Promise.all(commands.map((c) => runCommand(c.command)));
   for (let i = 0; i < results.length; i++) {
       const result = results[i];
       benchmarks.push({ 'language': commands[i].language, 'method': commands[i].method, ...stats(result.times) });
   }
   if (verbose) {
       const rawResults = results.map((r, i) => ({ 'language': commands[i].language, 'method': commands[i].method, ...stats(r.times), 'times': r.times }));
       console.log(rawResults);
   }
   console.log(asTable(benchmarks));
}
const [_, , exchangeId, methodName, ...params] = process.argv.filter((x) => !x.startsWith('--'));
const verbose = process.argv.includes('--verbose');
const minIterationsString = process.argv.find((x) => x.startsWith('--min-iterations='))?.slice(18);
const minIterations = minIterationsString ? parseInt(minIterationsString) : 10;
const argsv = process.argv.filter((x) => x.startsWith('--') && !x.startsWith('--min-iterations')).join(' ');
console.log((new Date()).toISOString());
console.log('Node.js:', process.version);
console.log('CCXT v' + version);
const start = new Date().getTime();
await benchmark(exchangeId, methodName, params, verbose, minIterations, argsv);
const end = new Date().getTime();
console.log((new Date().toISOString()), 'Total time:', end - start, 'ms');
process.exit(0);