eth-gas-reporter
Advanced tools
Comparing version 0.0.7 to 0.0.8
122
gasStats.js
@@ -14,2 +14,3 @@ /** | ||
const blockLimit = 6718946; | ||
/** | ||
@@ -34,3 +35,3 @@ * Expresses gas usage as a nation-state currency price | ||
*/ | ||
function gasToPercentOfLimit(gasUsed, blockLimit = 6718946){ | ||
function gasToPercentOfLimit(gasUsed){ | ||
return Math.round(1000 * gasUsed / blockLimit) / 10; | ||
@@ -49,6 +50,6 @@ } | ||
/** | ||
* Prints a gas stats table to stdout. Source: Gnosis / Alan Lu (see issues) | ||
* Prints a gas stats table to stdout. Based on Alan Lu's stats for Gnosis | ||
* @param {Object} methodMap methods and their gas usage (from mapMethodToContracts) | ||
*/ | ||
async function generateGasStatsReport (methodMap) { | ||
async function generateGasStatsReport (methodMap, deployMap) { | ||
const { | ||
@@ -61,3 +62,3 @@ currency, | ||
// Compose rows | ||
const rows = []; | ||
const methodRows = []; | ||
@@ -86,3 +87,5 @@ _.forEach(methodMap, (data, methodId) => { | ||
section = []; | ||
stats.numberOfCalls = data.numberOfCalls.toString().grey; | ||
const section = []; | ||
section.push(data.contract.grey); | ||
@@ -93,7 +96,41 @@ section.push(data.method) | ||
section.push({hAlign: 'right', content: stats.average}) | ||
section.push({hAlign: 'right', content: stats.numberOfCalls}) | ||
section.push({hAlign: 'right', content: stats.cost.toString().green}) | ||
rows.push(section); | ||
methodRows.push(section); | ||
}) | ||
const deployRows = []; | ||
deployMap.sort((a,b) => a.name.localeCompare(b.name)); | ||
deployMap.forEach(contract => { | ||
let stats = {}; | ||
if (!contract.gasData.length) return; | ||
const total = contract.gasData.reduce((acc, datum) => acc + datum, 0) | ||
stats.average = Math.round(total / contract.gasData.length) | ||
stats.percent = gasToPercentOfLimit(stats.average); | ||
stats.cost = (ethPrice && gasPrice) ? gasToCost(stats.average, ethPrice, gasPrice) : '-'.grey; | ||
const sortedData = contract.gasData.sort((a,b) => a - b); | ||
stats.min = sortedData[0] | ||
stats.max = sortedData[sortedData.length - 1] | ||
const uniform = (stats.min === stats.max); | ||
stats.min = (uniform) ? '-' : stats.min.toString().yellow; | ||
stats.max = (uniform) ? '-' : stats.max.toString().red; | ||
section = []; | ||
section.push({hAlign: 'left', colSpan: 2, content: contract.name}); | ||
section.push({hAlign: 'right', content: stats.min}) | ||
section.push({hAlign: 'right', content: stats.max}) | ||
section.push({hAlign: 'right', content: stats.average}) | ||
section.push({hAlign: 'right', content: `${stats.percent} %`.grey}) | ||
section.push({hAlign: 'right', content: stats.cost.toString().green}) | ||
deployRows.push(section); | ||
}); | ||
// Format table | ||
@@ -107,3 +144,9 @@ const table = new Table({ | ||
let title; | ||
// Format and load methods metrics | ||
let title = [ | ||
{hAlign: 'center', colSpan: 5, content: 'Gas Usage Metrics'.green.bold}, | ||
{hAlign: 'center', colSpan: 2, content: `Block limit: ${blockLimit} gas`.grey } | ||
]; | ||
let methodSubtitle; | ||
if (ethPrice && gasPrice){ | ||
@@ -113,25 +156,27 @@ const gwei = parseInt(gasPrice) * 1e-9; | ||
title = [ | ||
{hAlign: 'center', colSpan: 2, content: 'Gas Usage Analysis'.green.bold}, | ||
methodSubtitle = [ | ||
{hAlign: 'left', colSpan: 2, content: 'Methods'.green.bold}, | ||
{hAlign: 'center', colSpan: 3, content: `${gwei} gwei/gas`.grey}, | ||
{hAlign: 'center', colSpan: 2, content: `${rate} ${currency.toLowerCase()}/eth`.red}, | ||
{hAlign: 'center', colSpan: 2, content: `${gwei} gwei/gas`.grey}, | ||
]; | ||
} else { | ||
title = [{hAlign: 'center', colSpan: 6, content: 'Gas Analytics'.green.bold}]; | ||
methodSubtitle = [{hAlign: 'left', colSpan: 7, content: 'Methods'.green.bold}]; | ||
} | ||
const header = [ | ||
'Contract'.bold, | ||
'Method'.bold, | ||
'Min'.green, | ||
'Max'.green, | ||
'Avg'.green, | ||
`${currency.toLowerCase()} (avg)`.bold | ||
] | ||
'Contract'.bold, | ||
'Method'.bold, | ||
'Min'.green, | ||
'Max'.green, | ||
'Avg'.green, | ||
'# calls'.bold, | ||
`${currency.toLowerCase()} (avg)`.bold | ||
] | ||
table.push(title); | ||
table.push(methodSubtitle); | ||
table.push(header); | ||
// Sort rows by contract, then method and push | ||
rows.sort((a,b) => { | ||
methodRows.sort((a,b) => { | ||
const contractName = a[0].localeCompare(b[0]); | ||
@@ -142,4 +187,14 @@ const methodName = a[1].localeCompare(b[1]); | ||
rows.forEach(row => table.push(row)); | ||
methodRows.forEach(row => table.push(row)); | ||
if (deployRows.length){ | ||
const deploymentsSubtitle = [ | ||
{hAlign: 'left', colSpan: 2, content: 'Deployments'.green.bold}, | ||
{hAlign: 'right', colSpan: 3, content: '' }, | ||
{hAlign: 'left', colSpan: 1, content: `% of limit`.bold} | ||
]; | ||
table.push(deploymentsSubtitle); | ||
deployRows.forEach(row => table.push(row)); | ||
} | ||
@@ -224,2 +279,3 @@ console.log(table.toString()) | ||
const methodMap = {} | ||
const deployMap = [] | ||
const abis = [] | ||
@@ -235,4 +291,11 @@ | ||
// Load all the artifacts | ||
// Create Deploy Map: | ||
const contract = truffleArtifacts.require(name) | ||
const contractInfo = { | ||
name: name.split('.sol')[0], | ||
binary: contract.unlinked_binary, | ||
gasData: [] | ||
} | ||
deployMap.push(contractInfo) | ||
abis.push(contract._json.abi) | ||
@@ -244,3 +307,3 @@ | ||
// Create Map; | ||
// Create Method Map; | ||
Object.keys(methodIDs).forEach(key => { | ||
@@ -255,3 +318,4 @@ const isConstant = methodIDs[key].constant | ||
method: methodIDs[key].name, | ||
gasData: [] | ||
gasData: [], | ||
numberOfCalls: 0 | ||
} | ||
@@ -264,3 +328,7 @@ } | ||
abis.forEach(abi => abiDecoder.addABI(abi)) | ||
return methodMap | ||
return { | ||
methodMap: methodMap, | ||
deployMap: deployMap | ||
} | ||
} | ||
@@ -275,7 +343,7 @@ | ||
module.exports = { | ||
mapMethodsToContracts: mapMethodsToContracts, | ||
getMethodID: getMethodID, | ||
getGasAndPriceRates: getGasAndPriceRates, | ||
gasToPercentOfLimit: gasToPercentOfLimit, | ||
generateGasStatsReport: generateGasStatsReport, | ||
getGasAndPriceRates: getGasAndPriceRates, | ||
getMethodID: getMethodID, | ||
mapMethodsToContracts: mapMethodsToContracts, | ||
pretty: pretty | ||
@@ -282,0 +350,0 @@ } |
59
index.js
@@ -17,3 +17,5 @@ const mocha = require('mocha') | ||
let startBlock | ||
let deployStartBlock | ||
let methodMap | ||
let deployMap | ||
@@ -23,3 +25,3 @@ // ------------------------------------ Helpers ------------------------------------------------- | ||
const gasAnalytics = (methodMap) => { | ||
const methodAnalytics = (methodMap) => { | ||
let gasUsed = 0 | ||
@@ -35,7 +37,11 @@ const endBlock = web3.eth.blockNumber | ||
methodMap && block.transactions.forEach(tx => { | ||
const input = web3.eth.getTransaction(tx).input; | ||
const transaction = web3.eth.getTransaction(tx); | ||
const receipt = web3.eth.getTransactionReceipt(tx); | ||
const id = stats.getMethodID( input ); | ||
if (methodMap[id]){ | ||
const id = stats.getMethodID( transaction.input ); | ||
const threw = receipt.gasUsed === transaction.gas; // Change this @ Byzantium | ||
if (methodMap[id] && !threw){ | ||
methodMap[id].gasData.push(receipt.gasUsed); | ||
methodMap[id].numberOfCalls++; | ||
} | ||
@@ -49,5 +55,27 @@ }) | ||
const deployAnalytics = (deployMap) => { | ||
const endBlock = web3.eth.blockNumber | ||
while(deployStartBlock <= endBlock){ | ||
const block = web3.eth.getBlock(deployStartBlock) | ||
block && block.transactions.forEach(tx => { | ||
const transaction = web3.eth.getTransaction(tx) | ||
const receipt = web3.eth.getTransactionReceipt(tx); | ||
if (receipt.contractAddress){ | ||
const match = deployMap.filter(contract => { | ||
return (transaction.input.indexOf(contract.binary) === 0) | ||
})[0]; | ||
match && match.gasData.push(receipt.gasUsed); | ||
} | ||
}); | ||
deployStartBlock++ | ||
} | ||
} | ||
// ------------------------------------ Runners ------------------------------------------------- | ||
runner.on('start', () => { | ||
methodMap = stats.mapMethodsToContracts(artifacts) | ||
({ methodMap, deployMap } = stats.mapMethodsToContracts(artifacts)); | ||
log() | ||
@@ -73,2 +101,3 @@ }) | ||
runner.on('test', () => { deployStartBlock = web3.eth.blockNumber }) | ||
runner.on('hook end', () => { startBlock = web3.eth.blockNumber + 1 }) | ||
@@ -80,21 +109,16 @@ | ||
let gasUsedString | ||
let gasUsed = gasAnalytics(methodMap) | ||
let percent = stats.gasToPercentOfLimit(gasUsed); | ||
deployAnalytics(deployMap); | ||
let gasUsed = methodAnalytics(methodMap) | ||
if (gasUsed){ | ||
gasUsedString = color('checkmark', ' (%d gas)'); | ||
if (percent >= 100){ | ||
limitString = color('fail', ' (%d% of limit) ') | ||
} else { | ||
limitString = ' (%d% of limit) ' | ||
} | ||
fmt = indent() + | ||
color('checkmark', ' ' + Base.symbols.ok) + | ||
color('pass', ' %s') + | ||
gasUsedString + | ||
limitString | ||
gasUsedString; | ||
log(fmt, test.title, gasUsed, percent) | ||
log(fmt, test.title, gasUsed) | ||
} else { | ||
fmt = indent() + | ||
@@ -109,3 +133,2 @@ color('checkmark', ' ' + Base.symbols.ok) + | ||
runner.on('fail', test => { | ||
let gasUsed = gasAnalytics() | ||
let fmt = indent() + color('fail', ' %d) %s') | ||
@@ -117,3 +140,3 @@ log() | ||
runner.on('end', () => { | ||
stats.generateGasStatsReport (methodMap) | ||
stats.generateGasStatsReport (methodMap, deployMap) | ||
self.epilogue() | ||
@@ -120,0 +143,0 @@ }) |
@@ -5,2 +5,3 @@ var ConvertLib = artifacts.require('./ConvertLib.sol') | ||
var VariableCosts = artifacts.require('./VariableCosts.sol') | ||
var VariableConstructor = artifacts.require('./VariableConstructor'); | ||
@@ -13,2 +14,3 @@ module.exports = function (deployer) { | ||
deployer.deploy(VariableCosts); | ||
deployer.deploy(VariableConstructor) | ||
} |
{ | ||
"name": "eth-gas-reporter", | ||
"version": "0.0.6", | ||
"version": "0.0.7", | ||
"description": "Mocha reporter which shows gas used per unit test.", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -40,2 +40,11 @@ const VariableCosts = artifacts.require('./VariableCosts.sol'); | ||
}) | ||
it('methods that do not throw', async() => { | ||
await instance.methodThatThrows(false); | ||
}); | ||
it('methods that throw', async() => { | ||
try {await instance.methodThatThrows(true)} | ||
catch(e){} | ||
}); | ||
}) |
{ | ||
"name": "eth-gas-reporter", | ||
"version": "0.0.7", | ||
"version": "0.0.8", | ||
"description": "Mocha reporter which shows gas used per unit test.", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
# eth-gas-reporter | ||
Gas usage per unit test. Average gas usage per method. A mocha reporter. | ||
A mocha reporter for Truffle. | ||
+ Gas usage per unit test. | ||
+ Average gas usage per method. | ||
+ Contract deployment costs. | ||
+ Real currency costs. | ||
![screen shot 2017-10-12 at 8 02 57 pm](https://user-images.githubusercontent.com/7332026/31528529-b0d52178-af88-11e7-9c58-004ba4a7b360.png) | ||
![screen shot 2017-10-22 at 4 03 57 pm](https://user-images.githubusercontent.com/7332026/31867351-c45a5a80-b742-11e7-98dd-49051684e5fd.png) | ||
### Install | ||
```javascript | ||
// Truffle/mocha installed globally | ||
``` | ||
// Truffle installed globally | ||
npm install -g eth-gas-reporter | ||
// Truffle/mocha installed locally | ||
// Truffle installed locally (ProTip: This always works.) | ||
npm install --save-dev eth-gas-reporter | ||
@@ -24,3 +28,3 @@ ``` | ||
port: 8545, | ||
network_id: "*" // Match any network id | ||
network_id: "*" | ||
} | ||
@@ -33,7 +37,13 @@ }, | ||
``` | ||
### Thanks | ||
Everything here has been cannibalized from elsewhere and dropped into mocha's reporter facility. Many thanks to: | ||
+ [@maurelian](https://github.com/maurelian) - once posted a slack picture of mocha reporting gas output instead time. Idea from him. | ||
+ [@cag](https://github.com/cag) - wrote a really nice gas stats utility for the Gnosis suites. The table borrows from / is based his work. | ||
+ [Neufund](https://github.com/Neufund/ico-contracts) - expressing gas usage as a ratio of the block limit in their ico suite. That comes from them. | ||
### Usage Notes | ||
+ Euro/Eth rates are loaded at run-time from the `coinmarketcap` api | ||
+ Gas prices are `safe-low` and loaded at run-time from the `blockcypher` api | ||
+ Method calls that throw are filtered from the stats. | ||
+ Contracts that link to libraries are not shown in the deployments table. | ||
### Credits | ||
All the ideas in this utility have been borrowed from elsewhere. Many thanks to: | ||
+ [@maurelian](https://github.com/maurelian) - Mocha reporting gas instead of time is his idea. | ||
+ [@cag](https://github.com/cag) - The table borrows from / is based his gas statistics work for the Gnosis contracts. | ||
+ [Neufund](https://github.com/Neufund/ico-contracts) - Block limit size ratios for contract deployments and euro pricing are borrowed from their `ico-contracts` test suite. |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
30442
23
565
48