Skip to content

Commit

Permalink
render html template
Browse files Browse the repository at this point in the history
  • Loading branch information
steelgeek091 committed Sep 14, 2024
1 parent fae8fc2 commit c87032e
Show file tree
Hide file tree
Showing 8 changed files with 235 additions and 1 deletion.
17 changes: 17 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,7 @@ vergen-pretty = "0.3.4"
crossbeam-channel = "0.5.13"

inferno = "0.11.14"
handlebars = "4.2.2"

# Note: the BEGIN and END comments below are required for external tooling. Do not remove.
# BEGIN MOVE DEPENDENCIES
Expand Down
5 changes: 4 additions & 1 deletion crates/rooch-gas-profiling/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,7 @@ move-core-types = { workspace = true }
move-vm-types = { workspace = true }
move-binary-format = { workspace = true }
anyhow = { workspace = true }
regex = "1.10.6"
regex = { workspace = true }
serde_json = { workspace = true }
smallvec = { workspace = true }
handlebars = { workspace = true }
96 changes: 96 additions & 0 deletions crates/rooch-gas-profiling/src/aggregate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright (c) RoochNetwork
// SPDX-License-Identifier: Apache-2.0

use crate::log::ExecutionAndIOCosts;
use crate::log::ExecutionGasEvent;
use crate::render::Render;
use move_core_types::gas_algebra::{GasQuantity, InternalGas};
use std::collections::{btree_map, BTreeMap};

/// Represents an aggregation of execution gas events, including the count and total gas costs for each type of event.
///
/// The events are sorted by the amount of gas used, from high to low.
#[derive(Debug)]
pub struct AggregatedExecutionGasEvents {
pub ops: Vec<(String, usize, InternalGas)>,
}

fn insert_or_add<K, U>(
map: &mut BTreeMap<K, (usize, GasQuantity<U>)>,
key: K,
amount: GasQuantity<U>,
) where
K: Ord,
{
if amount.is_zero() {
return;
}
match map.entry(key) {
btree_map::Entry::Occupied(entry) => {
let r = entry.into_mut();
r.0 += 1;
r.1 += amount;
}
btree_map::Entry::Vacant(entry) => {
entry.insert((1, amount));
}
}
}

fn into_sorted_vec<I, K, N>(collection: I) -> Vec<(K, usize, N)>
where
N: Ord,
I: IntoIterator<Item = (K, (usize, N))>,
{
let mut v = collection
.into_iter()
.map(|(key, (count, amount))| (key, count, amount))
.collect::<Vec<_>>();
// Sort in descending order.
v.sort_by(|(_key1, _count1, amount1), (_key2, _count2, amount2)| amount2.cmp(amount1));
v
}

impl ExecutionAndIOCosts {
/// Counts the number of hits and aggregates the gas costs for each type of event.
pub fn aggregate_gas_events(&self) -> AggregatedExecutionGasEvents {
use ExecutionGasEvent::*;

let mut ops = BTreeMap::new();
let mut storage_reads = BTreeMap::new();

for event in self.gas_events() {
match event {
Loc(..) | Call(..) => (),
Bytecode { op, cost } => insert_or_add(
&mut ops,
format!("{:?}", op).to_ascii_lowercase().to_string(),
*cost,
),
CallNative {
module_id,
fn_name,
ty_args,
cost,
} => insert_or_add(
&mut ops,
format!(
"{}",
Render(&(module_id, fn_name.as_ident_str(), ty_args.as_slice())),
),
*cost,
),
LoadResource {
addr: _addr,
ty,
cost,
} => insert_or_add(&mut storage_reads, format!("{}", ty), *cost),
CreateTy { cost } => insert_or_add(&mut ops, "create_ty".to_string(), *cost),
}
}

AggregatedExecutionGasEvents {
ops: into_sorted_vec(ops),
}
}
}
3 changes: 3 additions & 0 deletions crates/rooch-gas-profiling/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ pub mod log;
pub mod misc;
pub mod profiler;
pub mod render;
pub mod report;

pub mod aggregate;
39 changes: 39 additions & 0 deletions crates/rooch-gas-profiling/src/log.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use move_core_types::account_address::AccountAddress;
use move_core_types::gas_algebra::InternalGas;
use move_core_types::identifier::Identifier;
use move_core_types::language_storage::{ModuleId, TypeTag};
use smallvec::{smallvec, SmallVec};

/// An event occurred during the execution of a function, along with the
/// gas cost associated with it, if any.
Expand Down Expand Up @@ -88,3 +89,41 @@ pub struct TransactionGasLog {
pub exec_io: ExecutionAndIOCosts,
pub storage: InternalGas,
}

pub struct GasEventIter<'a> {
stack: SmallVec<[(&'a CallFrame, usize); 16]>,
}

impl<'a> Iterator for GasEventIter<'a> {
type Item = &'a ExecutionGasEvent;

fn next(&mut self) -> Option<Self::Item> {
loop {
match self.stack.last_mut() {
None => return None,
Some((frame, pc)) => {
if *pc >= frame.events.len() {
self.stack.pop();
continue;
}

let event = &frame.events[*pc];
*pc += 1;
if let ExecutionGasEvent::Call(child_frame) = event {
self.stack.push((child_frame, 0))
}
return Some(event);
}
}
}
}
}

impl ExecutionAndIOCosts {
#[allow(clippy::needless_lifetimes)]
pub fn gas_events<'a>(&'a self) -> GasEventIter<'a> {
GasEventIter {
stack: smallvec![(&self.call_graph, 0)],
}
}
}
75 changes: 75 additions & 0 deletions crates/rooch-gas-profiling/src/report.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright (c) RoochNetwork
// SPDX-License-Identifier: Apache-2.0

use crate::log::TransactionGasLog;
use anyhow::Result;
use handlebars::Handlebars;
use move_core_types::gas_algebra::InternalGas;
use serde_json::{json, Map, Value};
use std::fs;
use std::path::Path;

const TEMPLATE: &str = include_str!("../templates/index.html");

fn ensure_dirs_exist(path: impl AsRef<Path>) -> Result<()> {
if let Err(err) = fs::create_dir_all(&path) {
match err.kind() {
std::io::ErrorKind::AlreadyExists => (),
_ => return Err(err.into()),
}
}
Ok(())
}

impl TransactionGasLog {
pub fn generate_html_report(&self, path: impl AsRef<Path>, header: String) -> Result<()> {
let mut data = Map::new();
data.insert("title".to_string(), Value::String(header));

let graph_exec_io = self.exec_io.to_flamegraph("Execution & IO".to_string())?;

let aggregated: crate::aggregate::AggregatedExecutionGasEvents =
self.exec_io.aggregate_gas_events();

let scaling_factor = u64::from(1u32) as f64;
let total_exec_io = u64::from(self.exec_io.total) as f64;

let convert_op = |(op, hits, cost): (String, usize, InternalGas)| {
let cost_scaled = format!("{:.8}", (u64::from(cost) as f64 / scaling_factor));
let cost_scaled = crate::misc::strip_trailing_zeros_and_decimal_point(&cost_scaled);

let percentage = format!("{:.2}%", u64::from(cost) as f64 / total_exec_io * 100.0);

json!({
"name": op,
"hits": hits,
"cost": cost_scaled,
"percentage": percentage,
})
};
data.insert(
"ops".to_string(),
Value::Array(aggregated.ops.into_iter().map(convert_op).collect()),
);

// Rendering the html doc
let mut handlebars = Handlebars::new();
handlebars.register_template_string("index", TEMPLATE)?;
let html = handlebars.render("index", &data)?;

// Writing to disk
let path_root = path.as_ref();

ensure_dirs_exist(path_root)?;
let path_assets = path_root.join("assets");
ensure_dirs_exist(&path_assets)?;

if let Some(graph_bytes) = graph_exec_io {
fs::write(path_assets.join("exec_io.svg"), graph_bytes)?;
}

fs::write(path_root.join("index.html"), html)?;

Ok(())
}
}
Empty file.

0 comments on commit c87032e

Please sign in to comment.