Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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 @@ -85,6 +85,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
142 changes: 138 additions & 4 deletions stackslib/src/net/api/blockreplay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,18 @@

use clarity::vm::costs::ExecutionCost;
use clarity::vm::Value;
#[cfg(all(feature = "profiler", target_os = "linux", target_arch = "x86_64"))]
use perf_event::events::Hardware;
#[cfg(all(feature = "profiler", target_os = "linux", target_arch = "x86_64"))]
use perf_event::{Builder, Counter};
use regex::{Captures, Regex};
use stacks_common::codec::StacksMessageCodec;
use stacks_common::types::chainstate::{BlockHeaderHash, ConsensusHash, StacksBlockId, TrieHash};
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 @@ -43,13 +48,15 @@ use crate::net::{Error as NetError, StacksHttpRequest, StacksNodeState};
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 +167,98 @@ impl RPCNakamotoBlockReplayRequestHandler {
for (i, tx) in block.txs.iter().enumerate() {
let tx_len = tx.tx_len();

#[cfg(all(feature = "profiler", target_os = "linux", target_arch = "x86_64"))]
let mut perf_event_cpu_instructions: Option<Counter> = None;
#[cfg(all(feature = "profiler", target_os = "linux", target_arch = "x86_64"))]
let mut perf_event_cpu_cycles: Option<Counter> = None;
#[cfg(all(feature = "profiler", target_os = "linux", target_arch = "x86_64"))]
let mut perf_event_cpu_ref_cycles: Option<Counter> = None;

#[cfg(all(feature = "profiler", target_os = "linux", target_arch = "x86_64"))]
if self.profiler {
if let Ok(mut perf_event_cpu_instructions_result) =
Builder::new(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) =
Builder::new(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) =
Builder::new(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);
}
}
}

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

#[cfg(feature = "profiler")]
let mut cpu_instructions: Option<u64> = None;
#[cfg(not(feature = "profiler"))]
let cpu_instructions: Option<u64> = None;

#[cfg(feature = "profiler")]
let mut cpu_cycles: Option<u64> = None;
#[cfg(not(feature = "profiler"))]
let cpu_cycles: Option<u64> = None;

#[cfg(feature = "profiler")]
let mut cpu_ref_cycles: Option<u64> = None;
#[cfg(not(feature = "profiler"))]
let cpu_ref_cycles: Option<u64> = None;

#[cfg(all(feature = "profiler", target_os = "linux", target_arch = "x86_64"))]
if self.profiler {
if let Some(mut perf_event_cpu_instructions) = 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) = 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) = 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);
}
}
}
}

let err = match tx_result {
TransactionResult::Success(tx_result) => {
txs_receipts.push(tx_result.receipt);
txs_receipts.push((
tx_result.receipt,
cpu_instructions,
cpu_cycles,
cpu_ref_cycles,
));
Ok(())
}
_ => Err(format!("Problematic tx {i}")),
Expand All @@ -194,8 +283,13 @@ 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, cpu_instructions, cpu_cycles, cpu_ref_cycles) in &txs_receipts {
let transaction = RPCReplayedBlockTransaction::from_receipt(
receipt,
cpu_instructions,
cpu_cycles,
cpu_ref_cycles,
);
rpc_replayed_block.transactions.push(transaction);
}

Expand Down Expand Up @@ -231,10 +325,19 @@ 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 {
pub fn from_receipt(
receipt: &StacksTransactionReceipt,
cpu_instructions: &Option<u64>,
cpu_cycles: &Option<u64>,
cpu_ref_cycles: &Option<u64>,
) -> Self {
let events = receipt
.events
.iter()
Expand Down Expand Up @@ -269,6 +372,9 @@ impl RPCReplayedBlockTransaction {
events,
post_condition_aborted: receipt.post_condition_aborted,
vm_error: receipt.vm_error.clone(),
cpu_instructions: cpu_instructions.clone(),
cpu_cycles: cpu_cycles.clone(),
cpu_ref_cycles: cpu_ref_cycles.clone(),
}
}
}
Expand Down Expand Up @@ -382,6 +488,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 +563,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
39 changes: 38 additions & 1 deletion stackslib/src/net/api/tests/blockreplay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,43 @@ fn test_try_parse_request() {
let (preamble, contents) = parsed_request.destruct();

assert_eq!(&preamble, request.preamble());
assert_eq!(handler.profiler, false);
}

#[test]
fn test_try_parse_request_with_profiler() {
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 33333);
let mut http = StacksHttp::new(addr.clone(), &ConnectionOptions::default());

let mut request = StacksHttpRequest::new_block_replay_with_profiler(
addr.into(),
&StacksBlockId([0x01; 32]),
true,
);

// add the authorization header
request.add_header("authorization".into(), "password".into());

let bytes = request.try_serialize().unwrap();

debug!("Request:\n{}\n", std::str::from_utf8(&bytes).unwrap());

let (parsed_preamble, offset) = http.read_preamble(&bytes).unwrap();

let mut handler =
blockreplay::RPCNakamotoBlockReplayRequestHandler::new(Some("password".into()));

let parsed_request = http
.handle_try_parse_request(
&mut handler,
&parsed_preamble.expect_request(),
&bytes[offset..],
)
.unwrap();

let (preamble, contents) = parsed_request.destruct();

assert_eq!(handler.profiler, true);
}

#[test]
Expand Down Expand Up @@ -111,7 +148,7 @@ fn test_try_make_response() {

// query existing, non-empty Nakamoto block
let mut request =
StacksHttpRequest::new_block_replay(addr.clone().into(), &rpc_test.canonical_tip);
StacksHttpRequest::new_block_replay_with_profiler(addr.clone().into(), &rpc_test.canonical_tip, true);
// add the authorization header
request.add_header("authorization".into(), "password".into());
requests.push(request);
Expand Down