Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add EVM profiler #2988

Merged
merged 7 commits into from
Aug 30, 2023
Merged

Add EVM profiler #2988

merged 7 commits into from
Aug 30, 2023

Conversation

jochem-brouwer
Copy link
Member

@jochem-brouwer jochem-brouwer commented Aug 24, 2023

This PR adds the feature for an EVM profiler. It features, once enabled, tracking:

  • The amount of calls, the time it takes, and the gas it consumes for each opcode
  • The same "inside" the precompiles

For precompiles we have the special case that the gas there is not tracked as in normal EVM calls, but instead depends upon what precompile is called. If a precompile is called, the "inner" gas used by that precompile (so without the overhead of the initial CALL opcode to the precompile) is tracked.

The VM gets extra options: profilerOpts, which can reportProfilerAfterBlock and reportProfilerAfterTx. Note that if a profiler is reported, the profiler in the EVM is cleared, so you cannot use both options.

EVM itself has an extra option as well, the profiler (should rename this to profilerOpts as well for consistency) which currently only has the enable flag. This will later support reporting the gas used for different hardforks (while the actual EVM runs on the hardfork it expects for that call) such that we can simulate running Shanghai gas on older blocks (since EVM had many underpriced situations back then).

To run and test:

Add this to BlockchainTestRunner:

  let vm = await VM.create({
    stateManager,
    blockchain,
    common,
    setHardfork: true,
    profilerOpts: {
      reportProfilerAfterBlock: true
    }
  })

Now run a blockchain test: npm run test:blockchain -- --fork=Shanghai --test=coinbaseWarmAccountCallGasFail_d3g0v0_Shanghai

Example output:

image

TODOs

  • Gas reported for CALL situations is incorrect, this encompasses setting up the CALL, but also all time consumed within that call frame
    • To test: DEBUG=ethjs,evm:profiler npm run test:blockchain -- --fork=Shanghai --test=ContractCreationSpam_d0g0v0_Shanghai
    • The total time of each opcode consumed is higher than the total test run
  • Test if precompile reporting is correct

TODOs for subsequent PRs

  • Add option to set custom hardfork for gas reporting to EVM
  • Add option to also profile the pre-EVM-call setup in VM, and also the post-EVM-call setup in VM
  • Add option for blockchain/state tests to --profile them

@codecov
Copy link

codecov bot commented Aug 24, 2023

Codecov Report

Merging #2988 (de824c8) into master (f5c6769) will decrease coverage by 0.37%.
The diff coverage is 47.72%.

Additional details and impacted files

Impacted file tree graph

Flag Coverage Δ
block 88.73% <ø> (ø)
blockchain 92.58% <ø> (ø)
client 87.39% <100.00%> (-0.06%) ⬇️
common 98.18% <ø> (ø)
ethash ∅ <ø> (∅)
evm 69.75% <56.60%> (-0.68%) ⬇️
rlp ∅ <ø> (?)
statemanager 84.46% <ø> (ø)
trie 90.07% <ø> (+0.25%) ⬆️
tx 96.38% <ø> (ø)
util 86.78% <ø> (ø)
vm 75.97% <16.36%> (-3.23%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

profilerOpts?: {
//evmProfilerOpts: EVMProfilerOpts
reportProfilerAfterTx?: boolean
reportProfilerAfterBlock?: boolean
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two quick nits: I would drop the Profiler here from the two options, so just reportAfterTx and reportAfterBlock, context is already clear and option gets shorter.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have explicitly added this profiler to VM, because we want to (?) profile VM pre-setup-before EVM call and post-setup-after-EVM call too. So after the EVM is ran, then VM will flush the StateManager and this can take some time in some situations, which we want to profile to (?)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was just about naming the options (please re-read in this context), so removing Profiler from the options names since it's redundant and makes the names long. 🙂

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah you are right, will update

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I misread your comment, I thought you meant to remove profilerOpts and put those options directly into (so without an encapsulating object)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would actually still be a fan of this name changes (can be along some follow-up work), so that we get to e.g. this._opts.profilerOpts?.reportAfterBlock (still long enough 🙂).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, addressed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would actually still be a fan of this name changes (can be along some follow-up work), so that we get to e.g. this._opts.profilerOpts?.reportAfterBlock (still long enough 🙂).

Yes, added this :)

@jochem-brouwer
Copy link
Member Author

image

describe: 'Report the VM profile after running each block',
boolean: true,
default: false,
})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh this is cool! 🤩

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't know that you had integrated so deeply into client already...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the only thing not to forget is; it does not work if you don't set the DEBUG=ethjs,evm:profiler env, so this is something we can improve (but not sure how to do this cleanly)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should just be able to set the debug variable programmatically with this process.env.DEBUG = 'ethjs,evm:profiler'. So just add a code block to the run command in the client that checks for either of these args and then sets the debug accordingly. Or better, do something like this:

if (args.vmProfileBlocks || args.vmProfileTxs) {
  let debugString = process.env.DEBUG
  if debugString.startsWith('ethjs') {
    debugString = debugString + ',evm:profiler'
  } else {
    debugString = 'ethjs,evm:profiler'
  }
  process.env.DEBUG=debugString
}   

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note, I haven't tested the above code but I do something like this in one of the trie demos to force it to output certain debug logs.

@jochem-brouwer jochem-brouwer marked this pull request as ready for review August 30, 2023 16:22
@jochem-brouwer
Copy link
Member Author

jochem-brouwer commented Aug 30, 2023

HEAD at 2e06d87 (backup)

Copy link
Contributor

@acolytec3 acolytec3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good, though left a couple of questions about syntax I'm not quite tracking.

@@ -171,6 +177,7 @@ export class Interpreter {
returnValue: undefined,
selfdestruct: new Set(),
}
;(this.profilerOpts = profilerOpts), (this.performanceLogger = performanceLogs)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this syntax?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No idea 😂 I definitely did not write this, I think I made a weird syntax mistake and then the linter did this. Will fix

packages/evm/src/interpreter.ts Show resolved Hide resolved
acolytec3
acolytec3 previously approved these changes Aug 30, 2023
Copy link
Contributor

@acolytec3 acolytec3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

holgerd77
holgerd77 previously approved these changes Aug 30, 2023
Copy link
Member

@holgerd77 holgerd77 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Member

@holgerd77 holgerd77 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still looks good to me 🙂

@jochem-brouwer jochem-brouwer merged commit 3c92910 into master Aug 30, 2023
41 of 42 checks passed
@holgerd77 holgerd77 deleted the evm-performance-logger branch August 30, 2023 20:36
@holgerd77
Copy link
Member

🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants