Cost model tracks builtins and bpf programs separately (#24468)

* Cost model tracks builtins and bpf programs separatele (enables adjusting block cost by actual bpf programs execution costs)

* Address reviews: expand test; add metrics stat
This commit is contained in:
Tao Zhu 2022-04-19 13:25:47 -05:00 committed by GitHub
parent 3bbfaae7b6
commit 94b0186a96
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 143 additions and 88 deletions

View File

@ -334,7 +334,8 @@ pub struct BatchedTransactionCostDetails {
pub batched_signature_cost: u64,
pub batched_write_lock_cost: u64,
pub batched_data_bytes_cost: u64,
pub batched_execute_cost: u64,
pub batched_builtins_execute_cost: u64,
pub batched_bpf_execute_cost: u64,
}
#[derive(Debug, Default)]
@ -1461,8 +1462,14 @@ impl BankingStage {
cost.data_bytes_cost
);
saturating_add_assign!(
batched_transaction_details.costs.batched_execute_cost,
cost.execution_cost
batched_transaction_details
.costs
.batched_builtins_execute_cost,
cost.builtins_execution_cost
);
saturating_add_assign!(
batched_transaction_details.costs.batched_bpf_execute_cost,
cost.bpf_execution_cost
);
}
Err(transaction_error) => match transaction_error {
@ -4362,39 +4369,38 @@ mod tests {
#[test]
fn test_accumulate_batched_transaction_costs() {
let tx_costs = vec![
TransactionCost {
signature_cost: 1,
write_lock_cost: 2,
data_bytes_cost: 3,
execution_cost: 10,
let signature_cost = 1;
let write_lock_cost = 2;
let data_bytes_cost = 3;
let builtins_execution_cost = 4;
let bpf_execution_cost = 10;
let num_txs = 4;
let tx_costs: Vec<_> = (0..num_txs)
.map(|_| TransactionCost {
signature_cost,
write_lock_cost,
data_bytes_cost,
builtins_execution_cost,
bpf_execution_cost,
..TransactionCost::default()
},
TransactionCost {
signature_cost: 4,
write_lock_cost: 5,
data_bytes_cost: 6,
execution_cost: 20,
..TransactionCost::default()
},
TransactionCost {
signature_cost: 7,
write_lock_cost: 8,
data_bytes_cost: 9,
execution_cost: 40,
..TransactionCost::default()
},
];
let tx_results = vec![
Ok(()),
Ok(()),
Err(TransactionError::WouldExceedMaxBlockCostLimit),
];
// should only accumulate first two cost that are OK
let expected_signatures = 5;
let expected_write_locks = 7;
let expected_data_bytes = 9;
let expected_executions = 30;
})
.collect();
let tx_results: Vec<_> = (0..num_txs)
.map(|n| {
if n % 2 == 0 {
Ok(())
} else {
Err(TransactionError::WouldExceedMaxBlockCostLimit)
}
})
.collect();
// should only accumulate half of the costs that are OK
let expected_signatures = signature_cost * (num_txs / 2);
let expected_write_locks = write_lock_cost * (num_txs / 2);
let expected_data_bytes = data_bytes_cost * (num_txs / 2);
let expected_builtins_execution_costs = builtins_execution_cost * (num_txs / 2);
let expected_bpf_execution_costs = bpf_execution_cost * (num_txs / 2);
let batched_transaction_details =
BankingStage::accumulate_batched_transaction_costs(tx_costs.iter(), tx_results.iter());
assert_eq!(
@ -4410,8 +4416,14 @@ mod tests {
batched_transaction_details.costs.batched_data_bytes_cost
);
assert_eq!(
expected_executions,
batched_transaction_details.costs.batched_execute_cost
expected_builtins_execution_costs,
batched_transaction_details
.costs
.batched_builtins_execute_cost
);
assert_eq!(
expected_bpf_execution_costs,
batched_transaction_details.costs.batched_bpf_execute_cost
);
}

View File

@ -252,8 +252,14 @@ impl QosService {
batched_transaction_details.costs.batched_data_bytes_cost,
Ordering::Relaxed,
);
self.metrics.stats.estimated_execute_cu.fetch_add(
batched_transaction_details.costs.batched_execute_cost,
self.metrics.stats.estimated_builtins_execute_cu.fetch_add(
batched_transaction_details
.costs
.batched_builtins_execute_cost,
Ordering::Relaxed,
);
self.metrics.stats.estimated_bpf_execute_cu.fetch_add(
batched_transaction_details.costs.batched_bpf_execute_cost,
Ordering::Relaxed,
);
@ -307,7 +313,7 @@ impl QosService {
pub fn accumulate_actual_execute_cu(&self, units: u64) {
self.metrics
.stats
.actual_execute_cu
.actual_bpf_execute_cu
.fetch_add(units, Ordering::Relaxed);
}
@ -376,11 +382,14 @@ struct QosServiceMetricsStats {
/// accumulated estimated instructino data Compute Units to be packed into block
estimated_data_bytes_cu: AtomicU64,
/// accumulated estimated program Compute Units to be packed into block
estimated_execute_cu: AtomicU64,
/// accumulated estimated builtin programs Compute Units to be packed into block
estimated_builtins_execute_cu: AtomicU64,
/// accumulated estimated BPF program Compute Units to be packed into block
estimated_bpf_execute_cu: AtomicU64,
/// accumulated actual program Compute Units that have been packed into block
actual_execute_cu: AtomicU64,
actual_bpf_execute_cu: AtomicU64,
/// accumulated actual program execute micro-sec that have been packed into block
actual_execute_time_us: AtomicU64,
@ -461,13 +470,22 @@ impl QosServiceMetrics {
i64
),
(
"estimated_execute_cu",
self.stats.estimated_execute_cu.swap(0, Ordering::Relaxed) as i64,
"estimated_builtins_execute_cu",
self.stats
.estimated_builtins_execute_cu
.swap(0, Ordering::Relaxed) as i64,
i64
),
(
"actual_execute_cu",
self.stats.actual_execute_cu.swap(0, Ordering::Relaxed) as i64,
"estimated_bpf_execute_cu",
self.stats
.estimated_bpf_execute_cu
.swap(0, Ordering::Relaxed) as i64,
i64
),
(
"actual_bpf_execute_cu",
self.stats.actual_bpf_execute_cu.swap(0, Ordering::Relaxed) as i64,
i64
),
(

View File

@ -394,6 +394,7 @@ fn execute_transactions(
inner_instructions,
durable_nonce_fee,
return_data,
..
} = details;
let lamports_per_signature = match durable_nonce_fee {

View File

@ -22,7 +22,8 @@ pub struct TransactionCost {
pub signature_cost: u64,
pub write_lock_cost: u64,
pub data_bytes_cost: u64,
pub execution_cost: u64,
pub builtins_execution_cost: u64,
pub bpf_execution_cost: u64,
pub account_data_size: u64,
pub is_simple_vote: bool,
}
@ -34,7 +35,8 @@ impl Default for TransactionCost {
signature_cost: 0u64,
write_lock_cost: 0u64,
data_bytes_cost: 0u64,
execution_cost: 0u64,
builtins_execution_cost: 0u64,
bpf_execution_cost: 0u64,
account_data_size: 0u64,
is_simple_vote: false,
}
@ -54,12 +56,17 @@ impl TransactionCost {
self.signature_cost = 0;
self.write_lock_cost = 0;
self.data_bytes_cost = 0;
self.execution_cost = 0;
self.builtins_execution_cost = 0;
self.bpf_execution_cost = 0;
self.is_simple_vote = false;
}
pub fn sum(&self) -> u64 {
self.signature_cost + self.write_lock_cost + self.data_bytes_cost + self.execution_cost
self.signature_cost
.saturating_add(self.write_lock_cost)
.saturating_add(self.data_bytes_cost)
.saturating_add(self.builtins_execution_cost)
.saturating_add(self.bpf_execution_cost)
}
}
@ -79,7 +86,6 @@ impl CostModel {
cost_table
.iter()
.map(|(key, cost)| (key, cost))
.chain(BUILT_IN_INSTRUCTION_COSTS.iter())
.for_each(|(program_id, cost)| {
self.upsert_instruction_cost(program_id, *cost);
});
@ -91,7 +97,8 @@ impl CostModel {
tx_cost.signature_cost = self.get_signature_cost(transaction);
self.get_write_lock_cost(&mut tx_cost, transaction);
tx_cost.data_bytes_cost = self.get_data_bytes_cost(transaction);
tx_cost.execution_cost = self.get_transaction_cost(transaction);
(tx_cost.builtins_execution_cost, tx_cost.bpf_execution_cost) =
self.get_transaction_cost(transaction);
tx_cost.account_data_size = self.calculate_account_data_size(transaction);
tx_cost.is_simple_vote = transaction.is_simple_vote_transaction();
@ -153,19 +160,25 @@ impl CostModel {
data_bytes_cost
}
fn get_transaction_cost(&self, transaction: &SanitizedTransaction) -> u64 {
let mut cost: u64 = 0;
fn get_transaction_cost(&self, transaction: &SanitizedTransaction) -> (u64, u64) {
let mut builtin_costs = 0u64;
let mut bpf_costs = 0u64;
for (program_id, instruction) in transaction.message().program_instructions_iter() {
let instruction_cost = self.find_instruction_cost(program_id);
trace!(
"instruction {:?} has cost of {}",
instruction,
instruction_cost
);
cost = cost.saturating_add(instruction_cost);
// to keep the same behavior, look for builtin first
if let Some(builtin_cost) = BUILT_IN_INSTRUCTION_COSTS.get(program_id) {
builtin_costs = builtin_costs.saturating_add(*builtin_cost);
} else {
let instruction_cost = self.find_instruction_cost(program_id);
trace!(
"instruction {:?} has cost of {}",
instruction,
instruction_cost
);
bpf_costs = bpf_costs.saturating_add(instruction_cost);
}
}
cost
(builtin_costs, bpf_costs)
}
fn calculate_account_data_size_on_deserialized_system_instruction(
@ -341,12 +354,13 @@ mod tests {
);
// expected cost for one system transfer instructions
let expected_cost = 8;
let expected_execution_cost = BUILT_IN_INSTRUCTION_COSTS
.get(&system_program::id())
.unwrap();
let mut testee = CostModel::default();
testee.upsert_instruction_cost(&system_program::id(), expected_cost);
let testee = CostModel::default();
assert_eq!(
expected_cost,
(*expected_execution_cost, 0),
testee.get_transaction_cost(&simple_transaction)
);
}
@ -368,12 +382,13 @@ mod tests {
debug!("many transfer transaction {:?}", tx);
// expected cost for two system transfer instructions
let program_cost = 8;
let program_cost = BUILT_IN_INSTRUCTION_COSTS
.get(&system_program::id())
.unwrap();
let expected_cost = program_cost * 2;
let mut testee = CostModel::default();
testee.upsert_instruction_cost(&system_program::id(), program_cost);
assert_eq!(expected_cost, testee.get_transaction_cost(&tx));
let testee = CostModel::default();
assert_eq!((expected_cost, 0), testee.get_transaction_cost(&tx));
}
#[test]
@ -405,7 +420,7 @@ mod tests {
// expected cost for two random/unknown program is
let expected_cost = testee.instruction_execution_cost_table.get_default_units() * 2;
assert_eq!(expected_cost, result);
assert_eq!((0, expected_cost), result);
}
#[test]
@ -472,13 +487,14 @@ mod tests {
));
let expected_account_cost = WRITE_LOCK_UNITS * 2;
let expected_execution_cost = 8;
let expected_execution_cost = BUILT_IN_INSTRUCTION_COSTS
.get(&system_program::id())
.unwrap();
let mut cost_model = CostModel::default();
cost_model.upsert_instruction_cost(&system_program::id(), expected_execution_cost);
let cost_model = CostModel::default();
let tx_cost = cost_model.calculate_cost(&tx);
assert_eq!(expected_account_cost, tx_cost.write_lock_cost);
assert_eq!(expected_execution_cost, tx_cost.execution_cost);
assert_eq!(*expected_execution_cost, tx_cost.builtins_execution_cost);
assert_eq!(2, tx_cost.writable_accounts.len());
}
@ -575,14 +591,14 @@ mod tests {
assert_eq!(*cost, cost_model.find_instruction_cost(id));
}
// verify built-in programs
// verify built-in programs are not in bpf_costs
assert!(cost_model
.instruction_execution_cost_table
.get_cost(&system_program::id())
.is_some());
.is_none());
assert!(cost_model
.instruction_execution_cost_table
.get_cost(&solana_vote_program::id())
.is_some());
.is_none());
}
}

View File

@ -296,7 +296,7 @@ mod tests {
system_transaction::transfer(mint_keypair, &keypair.pubkey(), 2, *start_hash),
);
let mut tx_cost = TransactionCost::new_with_capacity(1);
tx_cost.execution_cost = 5;
tx_cost.bpf_execution_cost = 5;
tx_cost.writable_accounts.push(mint_keypair.pubkey());
(simple_transaction, tx_cost)
@ -324,7 +324,7 @@ mod tests {
)
.unwrap();
let mut tx_cost = TransactionCost::new_with_capacity(1);
tx_cost.execution_cost = 10;
tx_cost.bpf_execution_cost = 10;
tx_cost.writable_accounts.push(mint_keypair.pubkey());
tx_cost.is_simple_vote = true;
@ -627,7 +627,7 @@ mod tests {
{
let tx_cost = TransactionCost {
writable_accounts: vec![acct1, acct2, acct3],
execution_cost: cost,
bpf_execution_cost: cost,
..TransactionCost::default()
};
assert!(testee.try_add(&tx_cost).is_ok());
@ -645,7 +645,7 @@ mod tests {
{
let tx_cost = TransactionCost {
writable_accounts: vec![acct2],
execution_cost: cost,
bpf_execution_cost: cost,
..TransactionCost::default()
};
assert!(testee.try_add(&tx_cost).is_ok());
@ -665,7 +665,7 @@ mod tests {
{
let tx_cost = TransactionCost {
writable_accounts: vec![acct1, acct2],
execution_cost: cost,
bpf_execution_cost: cost,
..TransactionCost::default()
};
assert!(testee.try_add(&tx_cost).is_err());

View File

@ -16,7 +16,8 @@ pub enum TransactionCostMetrics {
signature_cost: u64,
write_lock_cost: u64,
data_bytes_cost: u64,
execution_cost: u64,
builtins_execution_cost: u64,
bpf_execution_cost: u64,
},
}
@ -51,7 +52,8 @@ impl TransactionCostMetricsSender {
signature_cost: cost.signature_cost,
write_lock_cost: cost.write_lock_cost,
data_bytes_cost: cost.data_bytes_cost,
execution_cost: cost.execution_cost,
builtins_execution_cost: cost.builtins_execution_cost,
bpf_execution_cost: cost.bpf_execution_cost,
})
.unwrap_or_else(|err| {
warn!(
@ -92,7 +94,8 @@ impl TransactionCostMetricsService {
signature_cost,
write_lock_cost,
data_bytes_cost,
execution_cost,
builtins_execution_cost,
bpf_execution_cost,
} => {
// report transaction cost details per slot|signature
datapoint_trace!(
@ -102,7 +105,12 @@ impl TransactionCostMetricsService {
("signature_cost", signature_cost as i64, i64),
("write_lock_cost", write_lock_cost as i64, i64),
("data_bytes_cost", data_bytes_cost as i64, i64),
("execution_cost", execution_cost as i64, i64),
(
"builtins_execution_cost",
builtins_execution_cost as i64,
i64
),
("bpf_execution_cost", bpf_execution_cost as i64, i64),
);
}
}