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

feat: implement gas profiler. #3038

Merged
merged 7 commits into from
Aug 18, 2020
Merged

feat: implement gas profiler. #3038

merged 7 commits into from
Aug 18, 2020

Conversation

olonho
Copy link
Contributor

@olonho olonho commented Jul 25, 2020

Implement profiler for gas consumption.

Command like

cd runtime/near-vm-runner-standalone  &&  cargo run -- --wasm-file /work/near/near-linkdrop/res/linkdrop.wasm --method-name create_account --input '{"new_account_id": "zest.near", "new_public_key": "ed25519:C1BYE6i7bDoqccHuXfGTHXQzJsdhjN7MQ6h3iCRxycCY" }' --profile-gas

produces

------------------------------
Total gas: 12849996969996
Host gas: 239484430401 [1% total]
Action gas: 12247300236960 [95% total]
Wasm execution: 363212302635 [2% total]
------ Host functions --------
base -> 5295362220 [0% total, 2% host]
contract_compile_base -> 35445963 [0% total, 0% host]
contract_compile_bytes -> 42827632500 [0% total, 17% host]
read_memory_base -> 28708495200 [0% total, 11% host]
read_memory_byte -> 809683929 [0% total, 0% host]
write_memory_base -> 11215179444 [0% total, 4% host]
write_memory_byte -> 359537904 [0% total, 0% host]
read_register_base -> 7551495558 [0% total, 3% host]
read_register_byte -> 11433192 [0% total, 0% host]
write_register_base -> 8596567458 [0% total, 3% host]
write_register_byte -> 440981424 [0% total, 0% host]
utf8_decoding_base -> 6223558122 [0% total, 2% host]
utf8_decoding_byte -> 4082126706 [0% total, 1% host]
storage_write_base -> 64196736000 [0% total, 26% host]
storage_write_key_byte -> 352414335 [0% total, 0% host]
storage_write_value_byte -> 1706019645 [0% total, 0% host]
storage_read_base -> 56356845750 [0% total, 23% host]
storage_read_key_byte -> 154762665 [0% total, 0% host]
promise_return -> 560152386 [0% total, 0% host]
------ Actions --------
create_account -> 99607375000 [0% total]
function_call -> 2320006835710 [18% total]
transfer -> 115123062500 [0% total]
add_key -> 101765125000 [0% total]
new_receipt -> 9610797838750 [74% total]
------------------------------

@gitpod-io
Copy link

gitpod-io bot commented Jul 25, 2020

@@ -22,16 +22,19 @@ pub struct GasCounter {
prepaid_gas: Gas,
is_view: bool,
ext_costs_config: ExtCostsConfig,
/// Where to store profile data, if needed.
profile: Option<&'a mut Vec<u64>>,
Copy link
Contributor

Choose a reason for hiding this comment

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

Please hide it behind costs_counting flag and consider Rc<RefCell<Vec<u64>> also to avoid lifetime poisoning (when introduction of lifetime into one type leads to introduction of it in dependent types).

@@ -19,6 +19,8 @@ base64 = "0.11"
serde = { version = "1", features = ["derive"] }
sha2 = "0.8"
sha3 = "0.8"
strum = "0.18"
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's not add more dependencies, we are trying to keep near-vm-logic minimal because we have plans of compiling it to Wasm. Also strum is not a widely used library, let's implement enum iterator manually here.


near-vm-logic = { path = "../near-vm-logic", version = "1.0.0"}
near-vm-runner = { path = "../near-vm-runner", version = "1.0.0" }
near-vm-runner = { path = "../near-vm-runner", version = "1.0.0", features = ["wasmtime_vm"] }
Copy link
Contributor

Choose a reason for hiding this comment

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

Typo?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nope, intentional, so that we could run in standalone mode with both VMs.

Copy link
Member

@ailisp ailisp left a comment

Choose a reason for hiding this comment

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

Looks good, maybe more helpful to output number as space seperate columns at each line begining, so unix sort command can list cost from max to minimum easier

@codecov
Copy link

codecov bot commented Jul 28, 2020

Codecov Report

Merging #3038 into master will not change coverage.
The diff coverage is n/a.

Impacted file tree graph

@@           Coverage Diff           @@
##           master    #3038   +/-   ##
=======================================
  Coverage   87.82%   87.82%           
=======================================
  Files         213      213           
  Lines       42377    42377           
=======================================
  Hits        37219    37219           
  Misses       5158     5158           

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 70f4e8a...db69143. Read the comment docs.

Copy link
Contributor

@MaksymZavershynskyi MaksymZavershynskyi left a comment

Choose a reason for hiding this comment

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

Looks good overall. There are some comments.

@@ -64,29 +75,81 @@ impl GasCounter {

#[cfg(feature = "costs_counting")]
#[inline]
fn inc_ext_costs_counter(&self, cost: ExtCosts, value: u64) {
fn inc_ext_costs_counter(&mut self, cost: ExtCosts, value: u64) {
EXT_COSTS_COUNTER.with(|f| {
Copy link
Contributor

Choose a reason for hiding this comment

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

I thought you suggested to remove EXT_COSTS_COUNTER because it is an expensive thread-local tool. If we have profile that it looks like it should be easy to remove EXT_COSTS_COUNTER

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well, they have different semantics with profile (profile sums costs, EXT_COSTS_COUNTER counts invocations) and EXT_COSTS_COUNTER id being used in the cost estimator, so I decided to keep it.

self.deduct_gas(burn_gas, use_gas)
}

/// A helper function to pay base cost gas fee for batching an action.
/// # Args:
/// * `base_fee`: base fee for the action;
/// * `sir`: whether the receiver_id is same as the current account ID;
pub fn pay_action_base(&mut self, base_fee: &Fee, sir: bool) -> Result<()> {
pub fn pay_action_base(&mut self, base_fee: &Fee, sir: bool, action: Actions) -> Result<()> {
Copy link
Contributor

Choose a reason for hiding this comment

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

We should just change pay_action_base and pay_acion_per_byte so that it takes action but does not take base_fee and then store RuntimeFeesConfig in GasCounter just like we store ExtCostsConfig. Then we would need to implement value() method for Actions just like we did it here: https://github.com/nearprotocol/nearcore/blob/f024a28d9b0f1b2238f04d288ddebc8ac12ff4d1/runtime/near-vm-logic/src/config.rs#L490

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is rather major change, do you believe we shall do that in this (somewhat unrelated) profiler change?

Copy link
Contributor

Choose a reason for hiding this comment

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

Let's create a tracking issue for it and fix it later.

ExtCosts::validator_total_stake_base as usize + 1
}

pub fn name_of(index: usize) -> &'static str {
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's implement Display trait instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I shall implement Display for which class? Note, that we have usize, not enum as an input, and usize -> ExtCosts conversion is not straightforward.

Copy link
Contributor

Choose a reason for hiding this comment

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

and usize -> ExtCosts conversion is not straightforward.

Why not? It should be a straightforward cast.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

int cannot be casted to enum, as far, as I could see.

@@ -541,4 +577,62 @@ impl ExtCosts {
validator_total_stake_base => config.validator_total_stake_base,
}
}

pub fn count() -> usize {
Copy link
Contributor

Choose a reason for hiding this comment

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

@@ -35,3 +37,5 @@ pub enum PromiseResult {
Successful(Vec<u8>),
Failed,
}

pub type ProfileData = Rc<RefCell<Vec<u64>>>;
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we try Rc<RefCell<[u8; Actions::count() + ExtCosts::count() + 1]>> once count() is const?

@@ -22,6 +22,8 @@ pub struct GasCounter {
prepaid_gas: Gas,
is_view: bool,
ext_costs_config: ExtCostsConfig,
/// Where to store profile data, if needed.
Copy link
Contributor

Choose a reason for hiding this comment

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

If I understood you correctly @olonho you said that it creates a negligible overhead if so then we can try to drop costs_counting flag once we get rid of the thread local static variable.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

"serde",
"serde_json",
"strum",
Copy link
Contributor

Choose a reason for hiding this comment

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

It does not look like strum is used anymore.

"serde",
"serde_json",
"strum",
Copy link
Contributor

Choose a reason for hiding this comment

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

It does not look like strum is used anymore.

@olonho
Copy link
Contributor Author

olonho commented Jul 30, 2020

Looks good, maybe more helpful to output number as space seperate columns at each line begining, so unix sort command can list cost from max to minimum easier

If sorting is needed, we'd better do that in code, but I guess it's not too big so that we could see relevant parts w/o sorting.

Copy link
Contributor

@MaksymZavershynskyi MaksymZavershynskyi left a comment

Choose a reason for hiding this comment

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

Approving, let's create a refactoring cleanup issue for tracking.

ExtCosts::validator_total_stake_base as usize + 1
}

pub fn name_of(index: usize) -> &'static str {
Copy link
Contributor

Choose a reason for hiding this comment

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

and usize -> ExtCosts conversion is not straightforward.

Why not? It should be a straightforward cast.

@@ -864,7 +867,7 @@ impl<'a> VMLogic<'a> {
.ok_or(HostError::IntegerOverflow)?;
}
use_gas = use_gas.checked_add(burn_gas).ok_or(HostError::IntegerOverflow)?;
self.gas_counter.deduct_gas(burn_gas, use_gas)
self.gas_counter.pay_action_accumulated(burn_gas, use_gas, Actions::new_receipt)
Copy link
Member

Choose a reason for hiding this comment

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

Hm, I might be wrong, but doesn't this has all the unused gas as well?
E.g. if we attach 100Tgas and use 20Tgas, this will be 80% usage because it will record 80 Tgas?

Copy link
Collaborator

Choose a reason for hiding this comment

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

it only records burnt gas in the profiler.

write!(
f,
"{}",
vec![
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is it possible to reuse name_of?

write!(
f,
"{}",
vec![
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe also reuse name_of

// Type of an action, used in fees logic.
#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug, PartialOrd, Ord)]
#[allow(non_camel_case_types)]
pub enum Actions {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't like the name of it, because it's confusing to Action. Is it possible to rename it to ActionCosts or ActionProfiler or something similar to indicate it's not an Action

Copy link
Collaborator

Choose a reason for hiding this comment

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

Also it doesn't have to be public. Can be pub(crate)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's exported to standalone runner, so not sure if could be crate pub.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This type is used outside of the crate, namely in standalone launcher.

@@ -35,3 +38,6 @@ pub enum PromiseResult {
Successful(Vec<u8>),
Failed,
}

/// Profile of gas consumption, +1 for Wasm bytecode execution cost.
pub type ProfileData = Rc<RefCell<[u64; Actions::count() + ExtCosts::count() + 1]>>;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we hide it behind the feature as well? It seems like this introduces RefCell and Rc towards the vm-logic from std, so this creates the dependencies.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As this type is used in signatures, it will clutter all code using it with the feature. Original code didn't use RefCell / Rc but had explicit lifetimes, and Max requested to rewrite it. What would we consider least evil?

@@ -22,6 +22,8 @@ pub struct GasCounter {
prepaid_gas: Gas,
is_view: bool,
ext_costs_config: ExtCostsConfig,
/// Where to store profile data, if needed.
profile: Option<ProfileData>,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Instead of put it into the constructor, can you put it using setProfile method. This way it can be behind a feature and doesn't affect existing implementations.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

But is it really correct for the cost breakdown be behind the flag?

action_gas += profile_data[e as usize + ExtCosts::count()];
}

let wasm_gas = profile_data[Actions::count() + ExtCosts::count()];
Copy link
Contributor

Choose a reason for hiding this comment

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

After some thought it might be good if we had the option to write to JSON file.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well, maybe, but not sure what usecase would be.

Copy link
Contributor

Choose a reason for hiding this comment

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

Well currently in near-sdk-as, it uses the rust binary and expects the output to be the JSON of the outcome. It is possible to work around this, but it would be a lot simpler if the only output is the outcome.

@olonho
Copy link
Contributor Author

olonho commented Aug 5, 2020

PTAL

@@ -108,21 +172,41 @@ impl GasCounter {
num_bytes.checked_mul(per_byte_fee.exec_fee()).ok_or(HostError::IntegerOverflow)?,
)
.ok_or(HostError::IntegerOverflow)?;

self.update_profile_action(action, burn_gas);
self.deduct_gas(burn_gas, use_gas)
}

/// A helper function to pay base cost gas fee for batching an action.
/// # Args:
/// * `base_fee`: base fee for the action;
/// * `sir`: whether the receiver_id is same as the current account ID;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please update comments

self.deduct_gas(burn_gas, use_gas)
}

pub fn pay_action_accumulated(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Add a comment

fn update_profile_wasm(&mut self, _value: u64) {}

pub fn pay_wasm_gas(&mut self, value: u64) -> Result<()> {
self.update_profile_wasm(value);
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should remove this and use difference from total

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

Successfully merging this pull request may close these issues.

7 participants