|
|
|
@ -7,7 +7,7 @@ const { argv } = require('yargs') |
|
|
|
|
.options({ |
|
|
|
|
style: { |
|
|
|
|
type: 'string', |
|
|
|
|
choices: [ 'shell', 'markdown' ], |
|
|
|
|
choices: ['shell', 'markdown'], |
|
|
|
|
default: 'shell', |
|
|
|
|
}, |
|
|
|
|
}); |
|
|
|
@ -16,52 +16,55 @@ const { argv } = require('yargs') |
|
|
|
|
const BASE_TX_COST = 21000; |
|
|
|
|
|
|
|
|
|
// Utilities
|
|
|
|
|
function sum (...args) { |
|
|
|
|
function sum(...args) { |
|
|
|
|
return args.reduce((a, b) => a + b, 0); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function average (...args) { |
|
|
|
|
function average(...args) { |
|
|
|
|
return sum(...args) / args.length; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function variation (current, previous, offset = 0) { |
|
|
|
|
function variation(current, previous, offset = 0) { |
|
|
|
|
return { |
|
|
|
|
value: current, |
|
|
|
|
delta: current - previous, |
|
|
|
|
prcnt: 100 * (current - previous) / (previous - offset), |
|
|
|
|
prcnt: (100 * (current - previous)) / (previous - offset), |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Report class
|
|
|
|
|
class Report { |
|
|
|
|
// Read report file
|
|
|
|
|
static load (filepath) { |
|
|
|
|
static load(filepath) { |
|
|
|
|
return JSON.parse(fs.readFileSync(filepath, 'utf8')); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Compare two reports
|
|
|
|
|
static compare (update, ref, opts = { hideEqual: true }) { |
|
|
|
|
static compare(update, ref, opts = { hideEqual: true }) { |
|
|
|
|
if (JSON.stringify(update.config.metadata) !== JSON.stringify(ref.config.metadata)) { |
|
|
|
|
throw new Error('Reports produced with non matching metadata'); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const deployments = update.info.deployments |
|
|
|
|
.map(contract => Object.assign( |
|
|
|
|
contract, |
|
|
|
|
{ previousVersion: ref.info.deployments.find(({ name }) => name === contract.name) }, |
|
|
|
|
)) |
|
|
|
|
.map(contract => |
|
|
|
|
Object.assign(contract, { previousVersion: ref.info.deployments.find(({ name }) => name === contract.name) }), |
|
|
|
|
) |
|
|
|
|
.filter(contract => contract.gasData?.length && contract.previousVersion?.gasData?.length) |
|
|
|
|
.flatMap(contract => [{ |
|
|
|
|
contract: contract.name, |
|
|
|
|
method: '[bytecode length]', |
|
|
|
|
avg: variation(contract.bytecode.length / 2 - 1, contract.previousVersion.bytecode.length / 2 - 1), |
|
|
|
|
}, { |
|
|
|
|
contract: contract.name, |
|
|
|
|
method: '[construction cost]', |
|
|
|
|
avg: variation( |
|
|
|
|
...[contract.gasData, contract.previousVersion.gasData].map(x => Math.round(average(...x))), |
|
|
|
|
BASE_TX_COST), |
|
|
|
|
}]) |
|
|
|
|
.flatMap(contract => [ |
|
|
|
|
{ |
|
|
|
|
contract: contract.name, |
|
|
|
|
method: '[bytecode length]', |
|
|
|
|
avg: variation(contract.bytecode.length / 2 - 1, contract.previousVersion.bytecode.length / 2 - 1), |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
contract: contract.name, |
|
|
|
|
method: '[construction cost]', |
|
|
|
|
avg: variation( |
|
|
|
|
...[contract.gasData, contract.previousVersion.gasData].map(x => Math.round(average(...x))), |
|
|
|
|
BASE_TX_COST, |
|
|
|
|
), |
|
|
|
|
}, |
|
|
|
|
]) |
|
|
|
|
.sort((a, b) => `${a.contract}:${a.method}`.localeCompare(`${b.contract}:${b.method}`)); |
|
|
|
|
|
|
|
|
|
const methods = Object.keys(update.info.methods) |
|
|
|
@ -77,21 +80,22 @@ class Report { |
|
|
|
|
})) |
|
|
|
|
.sort((a, b) => `${a.contract}:${a.method}`.localeCompare(`${b.contract}:${b.method}`)); |
|
|
|
|
|
|
|
|
|
return [].concat(deployments, methods) |
|
|
|
|
return [] |
|
|
|
|
.concat(deployments, methods) |
|
|
|
|
.filter(row => !opts.hideEqual || row.min?.delta || row.max?.delta || row.avg?.delta); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Display
|
|
|
|
|
function center (text, length) { |
|
|
|
|
function center(text, length) { |
|
|
|
|
return text.padStart((text.length + length) / 2).padEnd(length); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function plusSign (num) { |
|
|
|
|
function plusSign(num) { |
|
|
|
|
return num > 0 ? '+' : ''; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function formatCellShell (cell) { |
|
|
|
|
function formatCellShell(cell) { |
|
|
|
|
const format = chalk[cell?.delta > 0 ? 'red' : cell?.delta < 0 ? 'green' : 'reset']; |
|
|
|
|
return [ |
|
|
|
|
format((!isFinite(cell?.value) ? '-' : cell.value.toString()).padStart(8)), |
|
|
|
@ -100,7 +104,7 @@ function formatCellShell (cell) { |
|
|
|
|
]; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function formatCmpShell (rows) { |
|
|
|
|
function formatCmpShell(rows) { |
|
|
|
|
const contractLength = Math.max(8, ...rows.map(({ contract }) => contract.length)); |
|
|
|
|
const methodLength = Math.max(7, ...rows.map(({ method }) => method.length)); |
|
|
|
|
|
|
|
|
@ -113,54 +117,60 @@ function formatCmpShell (rows) { |
|
|
|
|
{ txt: 'Avg', length: 30 }, |
|
|
|
|
{ txt: '', length: 0 }, |
|
|
|
|
]; |
|
|
|
|
const HEADER = COLS.map(entry => chalk.bold(center(entry.txt, entry.length || 0))).join(' | ').trim(); |
|
|
|
|
const SEPARATOR = COLS.map(({ length }) => length > 0 ? '-'.repeat(length + 2) : '').join('|').trim(); |
|
|
|
|
const HEADER = COLS.map(entry => chalk.bold(center(entry.txt, entry.length || 0))) |
|
|
|
|
.join(' | ') |
|
|
|
|
.trim(); |
|
|
|
|
const SEPARATOR = COLS.map(({ length }) => (length > 0 ? '-'.repeat(length + 2) : '')) |
|
|
|
|
.join('|') |
|
|
|
|
.trim(); |
|
|
|
|
|
|
|
|
|
return [ |
|
|
|
|
'', |
|
|
|
|
HEADER, |
|
|
|
|
...rows.map(entry => [ |
|
|
|
|
'', |
|
|
|
|
chalk.grey(entry.contract.padEnd(contractLength)), |
|
|
|
|
entry.method.padEnd(methodLength), |
|
|
|
|
...formatCellShell(entry.min), |
|
|
|
|
...formatCellShell(entry.max), |
|
|
|
|
...formatCellShell(entry.avg), |
|
|
|
|
'', |
|
|
|
|
].join(' | ').trim()), |
|
|
|
|
...rows.map(entry => |
|
|
|
|
[ |
|
|
|
|
'', |
|
|
|
|
chalk.grey(entry.contract.padEnd(contractLength)), |
|
|
|
|
entry.method.padEnd(methodLength), |
|
|
|
|
...formatCellShell(entry.min), |
|
|
|
|
...formatCellShell(entry.max), |
|
|
|
|
...formatCellShell(entry.avg), |
|
|
|
|
'', |
|
|
|
|
] |
|
|
|
|
.join(' | ') |
|
|
|
|
.trim(), |
|
|
|
|
), |
|
|
|
|
'', |
|
|
|
|
].join(`\n${SEPARATOR}\n`).trim(); |
|
|
|
|
] |
|
|
|
|
.join(`\n${SEPARATOR}\n`) |
|
|
|
|
.trim(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function alignPattern (align) { |
|
|
|
|
function alignPattern(align) { |
|
|
|
|
switch (align) { |
|
|
|
|
case 'left': |
|
|
|
|
case undefined: |
|
|
|
|
return ':-'; |
|
|
|
|
case 'right': |
|
|
|
|
return '-:'; |
|
|
|
|
case 'center': |
|
|
|
|
return ':-:'; |
|
|
|
|
case 'left': |
|
|
|
|
case undefined: |
|
|
|
|
return ':-'; |
|
|
|
|
case 'right': |
|
|
|
|
return '-:'; |
|
|
|
|
case 'center': |
|
|
|
|
return ':-:'; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function trend (value) { |
|
|
|
|
return value > 0 |
|
|
|
|
? ':x:' |
|
|
|
|
: value < 0 |
|
|
|
|
? ':heavy_check_mark:' |
|
|
|
|
: ':heavy_minus_sign:'; |
|
|
|
|
function trend(value) { |
|
|
|
|
return value > 0 ? ':x:' : value < 0 ? ':heavy_check_mark:' : ':heavy_minus_sign:'; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function formatCellMarkdown (cell) { |
|
|
|
|
function formatCellMarkdown(cell) { |
|
|
|
|
return [ |
|
|
|
|
(!isFinite(cell?.value) ? '-' : cell.value.toString()), |
|
|
|
|
(!isFinite(cell?.delta) ? '-' : plusSign(cell.delta) + cell.delta.toString()), |
|
|
|
|
(!isFinite(cell?.prcnt) ? '-' : plusSign(cell.prcnt) + cell.prcnt.toFixed(2) + '%' + trend(cell.delta)), |
|
|
|
|
!isFinite(cell?.value) ? '-' : cell.value.toString(), |
|
|
|
|
!isFinite(cell?.delta) ? '-' : plusSign(cell.delta) + cell.delta.toString(), |
|
|
|
|
!isFinite(cell?.prcnt) ? '-' : plusSign(cell.prcnt) + cell.prcnt.toFixed(2) + '%' + trend(cell.delta), |
|
|
|
|
]; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function formatCmpMarkdown (rows) { |
|
|
|
|
function formatCmpMarkdown(rows) { |
|
|
|
|
const COLS = [ |
|
|
|
|
{ txt: '' }, |
|
|
|
|
{ txt: 'Contract', align: 'left' }, |
|
|
|
@ -176,36 +186,48 @@ function formatCmpMarkdown (rows) { |
|
|
|
|
{ txt: '%', align: 'right' }, |
|
|
|
|
{ txt: '' }, |
|
|
|
|
]; |
|
|
|
|
const HEADER = COLS.map(entry => entry.txt).join(' | ').trim(); |
|
|
|
|
const SEPARATOR = COLS.map(entry => entry.txt ? alignPattern(entry.align) : '').join('|').trim(); |
|
|
|
|
const HEADER = COLS.map(entry => entry.txt) |
|
|
|
|
.join(' | ') |
|
|
|
|
.trim(); |
|
|
|
|
const SEPARATOR = COLS.map(entry => (entry.txt ? alignPattern(entry.align) : '')) |
|
|
|
|
.join('|') |
|
|
|
|
.trim(); |
|
|
|
|
|
|
|
|
|
return [ |
|
|
|
|
'# Changes to gas costs', |
|
|
|
|
'', |
|
|
|
|
HEADER, |
|
|
|
|
SEPARATOR, |
|
|
|
|
rows.map(entry => [ |
|
|
|
|
'', |
|
|
|
|
entry.contract, |
|
|
|
|
entry.method, |
|
|
|
|
...formatCellMarkdown(entry.min), |
|
|
|
|
...formatCellMarkdown(entry.max), |
|
|
|
|
...formatCellMarkdown(entry.avg), |
|
|
|
|
'', |
|
|
|
|
].join(' | ').trim()).join('\n'), |
|
|
|
|
rows |
|
|
|
|
.map(entry => |
|
|
|
|
[ |
|
|
|
|
'', |
|
|
|
|
entry.contract, |
|
|
|
|
entry.method, |
|
|
|
|
...formatCellMarkdown(entry.min), |
|
|
|
|
...formatCellMarkdown(entry.max), |
|
|
|
|
...formatCellMarkdown(entry.avg), |
|
|
|
|
'', |
|
|
|
|
] |
|
|
|
|
.join(' | ') |
|
|
|
|
.trim(), |
|
|
|
|
) |
|
|
|
|
.join('\n'), |
|
|
|
|
'', |
|
|
|
|
].join('\n').trim(); |
|
|
|
|
] |
|
|
|
|
.join('\n') |
|
|
|
|
.trim(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// MAIN
|
|
|
|
|
const report = Report.compare(Report.load(argv._[0]), Report.load(argv._[1])); |
|
|
|
|
|
|
|
|
|
switch (argv.style) { |
|
|
|
|
case 'markdown': |
|
|
|
|
console.log(formatCmpMarkdown(report)); |
|
|
|
|
break; |
|
|
|
|
case 'shell': |
|
|
|
|
default: |
|
|
|
|
console.log(formatCmpShell(report)); |
|
|
|
|
break; |
|
|
|
|
case 'markdown': |
|
|
|
|
console.log(formatCmpMarkdown(report)); |
|
|
|
|
break; |
|
|
|
|
case 'shell': |
|
|
|
|
default: |
|
|
|
|
console.log(formatCmpShell(report)); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|