Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 61 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions stackslib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ developer-mode = ["clarity/developer-mode"]
monitoring_prom = ["prometheus"]
slog_json = ["stacks-common/slog_json", "clarity/slog_json", "pox-locking/slog_json"]
testing = ["chrono", "stacks-common/testing", "clarity/testing"]
profiler = ["perf-event2"]

[target.'cfg(all(target_os = "linux", target_arch = "x86_64"))'.dependencies]
perf-event2 = { version = "0.7.4", optional = true }

[target.'cfg(all(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64"), not(any(target_os="windows"))))'.dependencies]
sha2 = { version = "0.10", features = ["asm"] }
Expand Down
164 changes: 160 additions & 4 deletions stackslib/src/net/api/blockreplay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use stacks_common::types::net::PeerHost;
use stacks_common::util::hash::Sha512Trunc256Sum;
use stacks_common::util::secp256k1::MessageSignature;
use stacks_common::util::serde_serializers::prefix_hex_codec;
use url::form_urlencoded;

use crate::burnchains::Txid;
use crate::chainstate::burn::db::sortdb::SortitionDB;
Expand All @@ -39,17 +40,122 @@ use crate::net::http::{
use crate::net::httpcore::{RPCRequestHandler, StacksHttpResponse};
use crate::net::{Error as NetError, StacksHttpRequest, StacksNodeState};

#[cfg(all(feature = "profiler", target_os = "linux", target_arch = "x86_64"))]
struct BlockReplayProfiler {
perf_event_cpu_instructions: Option<perf_event::Counter>,
perf_event_cpu_cycles: Option<perf_event::Counter>,
perf_event_cpu_ref_cycles: Option<perf_event::Counter>,
}

#[cfg(not(all(feature = "profiler", target_os = "linux", target_arch = "x86_64")))]
struct BlockReplayProfiler();

#[derive(Default)]
struct BlockReplayProfilerResult {
cpu_instructions: Option<u64>,
cpu_cycles: Option<u64>,
cpu_ref_cycles: Option<u64>,
}

#[cfg(all(feature = "profiler", target_os = "linux", target_arch = "x86_64"))]
impl BlockReplayProfiler {
fn new() -> Self {
let mut perf_event_cpu_instructions: Option<perf_event::Counter> = None;
let mut perf_event_cpu_cycles: Option<perf_event::Counter> = None;
let mut perf_event_cpu_ref_cycles: Option<perf_event::Counter> = None;

if let Ok(mut perf_event_cpu_instructions_result) =
perf_event::Builder::new(perf_event::events::Hardware::INSTRUCTIONS).build()
{
if perf_event_cpu_instructions_result.enable().is_ok() {
perf_event_cpu_instructions = Some(perf_event_cpu_instructions_result);
}
}

if let Ok(mut perf_event_cpu_cycles_result) =
perf_event::Builder::new(perf_event::events::Hardware::CPU_CYCLES).build()
{
if perf_event_cpu_cycles_result.enable().is_ok() {
perf_event_cpu_cycles = Some(perf_event_cpu_cycles_result);
}
}

if let Ok(mut perf_event_cpu_ref_cycles_result) =
perf_event::Builder::new(perf_event::events::Hardware::REF_CPU_CYCLES).build()
{
if perf_event_cpu_ref_cycles_result.enable().is_ok() {
perf_event_cpu_ref_cycles = Some(perf_event_cpu_ref_cycles_result);
}
}

Self {
perf_event_cpu_instructions,
perf_event_cpu_cycles,
perf_event_cpu_ref_cycles,
}
}

fn collect(self) -> BlockReplayProfilerResult {
let mut cpu_instructions: Option<u64> = None;
let mut cpu_cycles: Option<u64> = None;
let mut cpu_ref_cycles: Option<u64> = None;

if let Some(mut perf_event_cpu_instructions) = self.perf_event_cpu_instructions {
if perf_event_cpu_instructions.disable().is_ok() {
if let Ok(value) = perf_event_cpu_instructions.read() {
cpu_instructions = Some(value);
}
}
}

if let Some(mut perf_event_cpu_cycles) = self.perf_event_cpu_cycles {
if perf_event_cpu_cycles.disable().is_ok() {
if let Ok(value) = perf_event_cpu_cycles.read() {
cpu_cycles = Some(value);
}
}
}

if let Some(mut perf_event_cpu_ref_cycles) = self.perf_event_cpu_ref_cycles {
if perf_event_cpu_ref_cycles.disable().is_ok() {
if let Ok(value) = perf_event_cpu_ref_cycles.read() {
cpu_ref_cycles = Some(value);
}
}
}

BlockReplayProfilerResult {
cpu_instructions,
cpu_cycles,
cpu_ref_cycles,
}
}
}

#[cfg(not(all(feature = "profiler", target_os = "linux", target_arch = "x86_64")))]
impl BlockReplayProfiler {
fn new() -> Self {
warn!("BlockReplay Profiler is not available in this build.");
Self {}
}
fn collect(self) -> BlockReplayProfilerResult {
BlockReplayProfilerResult::default()
}
}

#[derive(Clone)]
pub struct RPCNakamotoBlockReplayRequestHandler {
pub block_id: Option<StacksBlockId>,
pub auth: Option<String>,
pub profiler: bool,
}

impl RPCNakamotoBlockReplayRequestHandler {
pub fn new(auth: Option<String>) -> Self {
Self {
block_id: None,
auth,
profiler: false,
}
}

Expand Down Expand Up @@ -160,16 +266,28 @@ impl RPCNakamotoBlockReplayRequestHandler {
for (i, tx) in block.txs.iter().enumerate() {
let tx_len = tx.tx_len();

let mut profiler: Option<BlockReplayProfiler> = None;
let mut profiler_result = BlockReplayProfilerResult::default();

if self.profiler {
profiler = Some(BlockReplayProfiler::new());
}

let tx_result = builder.try_mine_tx_with_len(
&mut tenure_tx,
tx,
tx_len,
&BlockLimitFunction::NO_LIMIT_HIT,
None,
);

if let Some(profiler) = profiler {
profiler_result = profiler.collect();
}

let err = match tx_result {
TransactionResult::Success(tx_result) => {
txs_receipts.push(tx_result.receipt);
txs_receipts.push((tx_result.receipt, profiler_result));
Ok(())
}
_ => Err(format!("Problematic tx {i}")),
Expand All @@ -194,8 +312,8 @@ impl RPCNakamotoBlockReplayRequestHandler {
let mut rpc_replayed_block =
RPCReplayedBlock::from_block(block, block_fees, tenure_id, parent_block_id);

for receipt in &txs_receipts {
let transaction = RPCReplayedBlockTransaction::from_receipt(receipt);
for (receipt, profiler_result) in &txs_receipts {
let transaction = RPCReplayedBlockTransaction::from_receipt(receipt, &profiler_result);
rpc_replayed_block.transactions.push(transaction);
}

Expand Down Expand Up @@ -231,10 +349,17 @@ pub struct RPCReplayedBlockTransaction {
pub post_condition_aborted: bool,
/// optional vm error
pub vm_error: Option<String>,
/// profiling data based on linux perf_events
pub cpu_instructions: Option<u64>,
pub cpu_cycles: Option<u64>,
pub cpu_ref_cycles: Option<u64>,
}

impl RPCReplayedBlockTransaction {
pub fn from_receipt(receipt: &StacksTransactionReceipt) -> Self {
fn from_receipt(
receipt: &StacksTransactionReceipt,
profiler_result: &BlockReplayProfilerResult,
) -> Self {
let events = receipt
.events
.iter()
Expand Down Expand Up @@ -269,6 +394,9 @@ impl RPCReplayedBlockTransaction {
events,
post_condition_aborted: receipt.post_condition_aborted,
vm_error: receipt.vm_error.clone(),
cpu_instructions: profiler_result.cpu_instructions,
cpu_cycles: profiler_result.cpu_cycles,
cpu_ref_cycles: profiler_result.cpu_ref_cycles,
}
}
}
Expand Down Expand Up @@ -382,6 +510,17 @@ impl HttpRequest for RPCNakamotoBlockReplayRequestHandler {

self.block_id = Some(block_id);

if let Some(query_string) = query {
for (key, value) in form_urlencoded::parse(query_string.as_bytes()) {
if key == "profiler" {
if value == "1" {
self.profiler = true;
}
break;
}
}
}

Ok(HttpRequestContents::new().query_string(query))
}
}
Expand Down Expand Up @@ -446,6 +585,23 @@ impl StacksHttpRequest {
)
.expect("FATAL: failed to construct request from infallible data")
}

pub fn new_block_replay_with_profiler(
host: PeerHost,
block_id: &StacksBlockId,
profiler: bool,
) -> StacksHttpRequest {
StacksHttpRequest::new_for_peer(
host,
"GET".into(),
format!("/v3/blocks/replay/{block_id}"),
HttpRequestContents::new().query_arg(
"profiler".into(),
if profiler { "1".into() } else { "0".into() },
),
)
.expect("FATAL: failed to construct request from infallible data")
}
}

/// Decode the HTTP response
Expand Down
Loading
Loading