use std::collections::HashMap;
use std::fmt;
use dyn_clone::DynClone;
use fuel_types::ContractId;
use crate::prelude::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct InstructionLocation {
context: Option<ContractId>,
offset: u64,
}
#[cfg(feature = "serde")]
impl serde::Serialize for InstructionLocation {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
if let Some(ctx) = self.context {
serializer.serialize_str(&format!("{}:{}", ctx, self.offset))
} else {
serializer.serialize_str(&format!("{}", self.offset))
}
}
}
#[cfg(feature = "serde")]
struct InstructionLocationVisitor;
#[cfg(feature = "serde")]
impl<'de> serde::de::Visitor<'de> for InstructionLocationVisitor {
type Value = InstructionLocation;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("A valid instruction location")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
use std::str::FromStr;
Ok(if let Some((l, r)) = value.split_once(':') {
let context = Some(
ContractId::from_str(l)
.map_err(|_| serde::de::Error::custom("Invalid ContractId in InstructionLocation"))?,
);
let offset = r.parse().unwrap();
InstructionLocation { context, offset }
} else {
let offset = value.parse().unwrap();
InstructionLocation { context: None, offset }
})
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for InstructionLocation {
fn deserialize<D>(deserializer: D) -> Result<InstructionLocation, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_str(InstructionLocationVisitor)
}
}
impl InstructionLocation {
pub const fn new(context: Option<ContractId>, offset: u64) -> Self {
Self { context, offset }
}
pub const fn context(&self) -> Option<ContractId> {
self.context
}
pub const fn offset(&self) -> u64 {
self.offset
}
}
impl fmt::Display for InstructionLocation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Location({}, offset={})",
self.context
.map(|contract_id| format!(
"contract_id={}",
contract_id.iter().map(|b| format!("{:02x?}", b)).collect::<String>()
),)
.unwrap_or_else(|| "script".to_string()),
self.offset
)
}
}
type PerLocation<T> = HashMap<InstructionLocation, T>;
pub struct PerLocationIter<'a, T>(std::collections::hash_map::Iter<'a, InstructionLocation, T>);
impl<'a, T> Iterator for PerLocationIter<'a, T> {
type Item = (&'a InstructionLocation, &'a T);
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
}
pub struct PerLocationKeys<'a, T>(std::collections::hash_map::Keys<'a, InstructionLocation, T>);
impl<'a, T> Iterator for PerLocationKeys<'a, T> {
type Item = &'a InstructionLocation;
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
}
pub struct PerLocationValues<'a, T>(std::collections::hash_map::Values<'a, InstructionLocation, T>);
impl<'a, T> Iterator for PerLocationValues<'a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
}
pub trait ProfileReceiver: DynClone {
fn on_transaction(&mut self, state: &Result<ProgramState, InterpreterError>, data: &ProfilingData);
}
dyn_clone::clone_trait_object!(ProfileReceiver);
#[derive(Clone)]
pub struct StderrReceiver;
impl ProfileReceiver for StderrReceiver {
fn on_transaction(&mut self, state: &Result<ProgramState, InterpreterError>, data: &ProfilingData) {
eprintln!("PROFILER: {:?} {:?}", state, data);
}
}
#[derive(Default, Clone)]
pub struct Profiler {
receiver: Option<Box<dyn ProfileReceiver + Send + Sync>>,
data: ProfilingData,
}
impl Profiler {
pub fn on_transaction(&mut self, state_result: &Result<ProgramState, InterpreterError>) {
if let Some(r) = &mut self.receiver {
r.on_transaction(state_result, &self.data);
}
}
pub fn set_receiver(&mut self, receiver: Box<dyn ProfileReceiver + Send + Sync>) {
self.receiver = Some(receiver);
}
pub fn data(&self) -> &ProfilingData {
&self.data
}
pub fn data_mut(&mut self) -> &mut ProfilingData {
&mut self.data
}
}
impl fmt::Debug for Profiler {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Profiler(receiver={:?}, data=",
match self.receiver {
Some(_) => "enabled",
None => "disabled",
}
)?;
self.data.fmt(f)?;
write!(f, ")")
}
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ProfilingData {
#[cfg(feature = "profile-coverage")]
coverage: CoverageProfilingData,
#[cfg(feature = "profile-gas")]
gas: GasProfilingData,
}
impl ProfilingData {
#[cfg(feature = "profile-gas")]
pub fn gas(&self) -> &GasProfilingData {
&self.gas
}
#[cfg(feature = "profile-gas")]
pub fn gas_mut(&mut self) -> &mut GasProfilingData {
&mut self.gas
}
#[cfg(feature = "profile-coverage")]
pub fn coverage(&self) -> &CoverageProfilingData {
&self.coverage
}
#[cfg(feature = "profile-coverage")]
pub fn coverage_mut(&mut self) -> &mut CoverageProfilingData {
&mut self.coverage
}
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CoverageProfilingData {
executed: PerLocation<()>,
}
impl<'a> CoverageProfilingData {
pub fn get(&self, location: &InstructionLocation) -> bool {
self.executed.contains_key(location)
}
pub fn set(&mut self, location: InstructionLocation) {
self.executed.insert(location, ());
}
pub fn iter(&'a self) -> PerLocationKeys<'a, ()> {
PerLocationKeys(self.executed.keys())
}
}
impl fmt::Display for CoverageProfilingData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut items: Vec<_> = self.iter().collect();
items.sort();
writeln!(f, "{:?}", items)
}
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct GasProfilingData {
gas_use: PerLocation<u64>,
}
impl<'a> GasProfilingData {
pub fn get(&self, location: &InstructionLocation) -> u64 {
self.gas_use.get(location).copied().unwrap_or(0)
}
pub fn add(&mut self, location: InstructionLocation, amount: u64) {
*self.gas_use.entry(location).or_insert(0) += amount;
}
pub fn iter(&'a self) -> PerLocationIter<'a, u64> {
PerLocationIter(self.gas_use.iter())
}
pub fn keys(&'a self) -> PerLocationKeys<'a, u64> {
PerLocationKeys(self.gas_use.keys())
}
pub fn values(&'a self) -> PerLocationValues<'a, u64> {
PerLocationValues(self.gas_use.values())
}
}
impl fmt::Display for GasProfilingData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut items: Vec<(_, _)> = self.iter().collect();
items.sort();
for (addr, count) in items {
writeln!(f, "{}: {}", addr, count)?;
}
Ok(())
}
}