Skip to content

Commit 8e909a5

Browse files
authored
Visualizer flexibility (#237)
* sim-rs: select a scenario to visualize * sim-rs: move stream processing logic out of server * sim-rs: handle stream processing in web worker * sim-rs: fix cbor/json encoding * sim-rs: always report when streaming is done * sim-rs: update documentation
1 parent d288558 commit 8e909a5

28 files changed

+468
-520
lines changed

sim-rs/Cargo.lock

+31-17
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sim-rs/sim-cli/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ clap = { version = "4", features = ["derive"] }
1212
ctrlc = "3"
1313
figment = { version = "0.10", features = ["yaml"] }
1414
itertools = "0.14"
15+
minicbor-serde = { version = "0.3", features = ["alloc"] }
1516
netsim-core = { git = "https://github.com/input-output-hk/ce-netsim", rev = "9d1e26c" }
1617
pretty-bytes-rust = "0.3.0"
1718
rand = "0.9"
1819
statrs = "0.18"
1920
serde = { version = "1", features = ["derive"] }
20-
serde_cbor = "0.11"
2121
serde_json = "1"
2222
serde_yaml = "0.9"
2323
sim-core = { path = "../sim-core" }

sim-rs/sim-cli/src/events.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -573,7 +573,7 @@ impl OutputTarget {
573573
file.write_all(string.as_bytes()).await?;
574574
}
575575
OutputFormat::CborStream => {
576-
let bytes = serde_cbor::to_vec(&event)?;
576+
let bytes = minicbor_serde::to_vec(&event)?;
577577
file.write_all(&bytes).await?;
578578
}
579579
}

sim-rs/sim-core/src/events.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,11 @@ impl EventTracker {
280280
endorsement: block.endorsement.as_ref().map(|e| Endorsement {
281281
eb: self.to_endorser_block(e.eb),
282282
bytes: e.bytes,
283-
votes: e.votes.iter().map(|n| self.to_node(*n)).collect(),
283+
votes: e
284+
.votes
285+
.iter()
286+
.map(|(k, v)| (self.to_node(*k), *v))
287+
.collect(),
284288
}),
285289
transactions: block.transactions.iter().map(|tx| tx.id).collect(),
286290
});

sim-rs/sim-core/src/model.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ impl<Node: Display + Serialize> Serialize for EndorserBlockId<Node> {
138138
where
139139
S: serde::Serializer,
140140
{
141-
let mut obj = serializer.serialize_struct("EndorserBlockId", 4)?;
141+
let mut obj = serializer.serialize_struct("EndorserBlockId", 3)?;
142142
obj.serialize_field("id", &self.to_string())?;
143143
obj.serialize_field("slot", &self.slot)?;
144144
obj.serialize_field("producer", &self.producer)?;
@@ -178,7 +178,7 @@ impl<Node: Display + Serialize> Serialize for VoteBundleId<Node> {
178178
where
179179
S: serde::Serializer,
180180
{
181-
let mut obj = serializer.serialize_struct("VoteBundleId", 4)?;
181+
let mut obj = serializer.serialize_struct("VoteBundleId", 3)?;
182182
obj.serialize_field("id", &self.to_string())?;
183183
obj.serialize_field("slot", &self.slot)?;
184184
obj.serialize_field("producer", &self.producer)?;
@@ -204,5 +204,5 @@ pub enum NoVoteReason {
204204
pub struct Endorsement<Node: Display = NodeId> {
205205
pub eb: EndorserBlockId<Node>,
206206
pub bytes: u64,
207-
pub votes: Vec<Node>,
207+
pub votes: BTreeMap<Node, usize>,
208208
}

sim-rs/sim-core/src/sim/node.rs

+19-22
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ struct NodeLeiosState {
138138
ebs: BTreeMap<EndorserBlockId, EndorserBlockState>,
139139
ebs_by_slot: BTreeMap<u64, Vec<EndorserBlockId>>,
140140
votes_to_generate: BTreeMap<u64, usize>,
141-
votes_by_eb: BTreeMap<EndorserBlockId, Vec<NodeId>>,
141+
votes_by_eb: BTreeMap<EndorserBlockId, BTreeMap<NodeId, usize>>,
142142
votes: BTreeMap<VoteBundleId, VoteBundleState>,
143143
}
144144

@@ -311,12 +311,7 @@ impl Node {
311311
CpuTask::RBBlockGenerated(block) => {
312312
let mut time = cpu_times.rb_generation;
313313
if let Some(endorsement) = &block.endorsement {
314-
let nodes = endorsement
315-
.votes
316-
.iter()
317-
.copied()
318-
.collect::<HashSet<_>>()
319-
.len();
314+
let nodes = endorsement.votes.len();
320315
time += cpu_times.cert_generation_constant
321316
+ (cpu_times.cert_generation_per_node * nodes as u32)
322317
}
@@ -327,12 +322,7 @@ impl Node {
327322
let bytes: u64 = rb.transactions.iter().map(|tx| tx.bytes).sum();
328323
time += cpu_times.rb_validation_per_byte * (bytes as u32);
329324
if let Some(endorsement) = &rb.endorsement {
330-
let nodes = endorsement
331-
.votes
332-
.iter()
333-
.copied()
334-
.collect::<HashSet<_>>()
335-
.len();
325+
let nodes = endorsement.votes.len();
336326
time += cpu_times.cert_validation_constant
337327
+ (cpu_times.cert_validation_per_node * nodes as u32)
338328
}
@@ -732,14 +722,17 @@ impl Node {
732722
.leios
733723
.votes_by_eb
734724
.iter()
735-
.filter(|(eb, votes)| {
736-
votes.len() as u64 >= vote_threshold && !forbidden_slots.contains(&eb.slot)
725+
.filter_map(|(eb, votes)| {
726+
let vote_count: usize = votes.values().sum();
727+
if (vote_count as u64) < vote_threshold || forbidden_slots.contains(&eb.slot) {
728+
return None;
729+
}
730+
Some((eb, vote_count))
737731
})
738-
.max_by_key(|(eb, votes)| (self.count_txs_in_eb(eb), votes.len()))?;
732+
.max_by_key(|(eb, votes)| (self.count_txs_in_eb(eb), *votes))?;
739733

740734
let (block, votes) = self.leios.votes_by_eb.remove_entry(&block)?;
741-
let nodes = votes.iter().collect::<HashSet<_>>().len();
742-
let bytes = self.sim_config.sizes.cert(nodes);
735+
let bytes = self.sim_config.sizes.cert(votes.len());
743736

744737
Some(Endorsement {
745738
eb: block,
@@ -1120,11 +1113,13 @@ impl Node {
11201113
return Ok(());
11211114
}
11221115
for (eb, count) in votes.ebs.iter() {
1123-
self.leios
1116+
*self
1117+
.leios
11241118
.votes_by_eb
11251119
.entry(*eb)
11261120
.or_default()
1127-
.extend(std::iter::repeat(votes.id.producer).take(*count));
1121+
.entry(votes.id.producer)
1122+
.or_default() += count;
11281123
}
11291124
// We haven't seen these votes before, so propagate them to our neighbors
11301125
for peer in &self.consumers {
@@ -1264,11 +1259,13 @@ impl Node {
12641259
fn finish_generating_vote_bundle(&mut self, votes: VoteBundle) -> Result<()> {
12651260
self.tracker.track_votes_generated(&votes);
12661261
for (eb, count) in &votes.ebs {
1267-
self.leios
1262+
*self
1263+
.leios
12681264
.votes_by_eb
12691265
.entry(*eb)
12701266
.or_default()
1271-
.extend(std::iter::repeat(votes.id.producer).take(*count));
1267+
.entry(votes.id.producer)
1268+
.or_default() += count;
12721269
}
12731270
let votes = Arc::new(votes);
12741271
self.leios

ui/.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,8 @@ dist
128128
.yarn/build-state.yml
129129
.yarn/install-state.gz
130130
.pnp.*
131+
132+
# don't commit these large files until we're ready
133+
public/scenarios.json
134+
public/topologies
135+
public/traces

ui/README.md

+25-5
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,34 @@ Before you can run the UI, ensure you have the following installed:
1818

1919
2. **Generate a Trace**
2020

21-
Before running the UI, you need to generate a trace using the `sim-rs`
22-
simulation. Navigate to the `sim-rs` directory and run the following command:
21+
Before running the UI, you need to prepare a scenario for it to visualize. Add the following to a file named `public/scenarios.json`:
22+
23+
```json
24+
{
25+
"scenarios": [
26+
{
27+
"name": "My Scenario",
28+
"topology": "topologies/thousand.yaml",
29+
"duration": 30.0,
30+
"trace": "traces/myscenario.jsonl"
31+
}
32+
]
33+
}
34+
```
2335

24-
```bash
25-
cargo run --release test_data/thousand.yaml output/messages.jsonl -s 30
36+
Now add that topology to the public directory:
37+
38+
```sh
39+
mkdir -p public/topologies
40+
cp ../sim-rs/test-data/thousand.yaml public/topologies
2641
```
2742

28-
This command will generate a trace file at `output/messages.jsonl`.
43+
And generate a trace to visualize from the `sim-rs` directory:
44+
45+
```bash
46+
mkdir -p ../ui/public/traces
47+
cargo run --release test_data/thousand.yaml ../ui/public/traces/myscenario.jsonl -s 30
48+
```
2949

3050
3. **Run the UI**
3151

ui/docs/component.d2

+3-9
Original file line numberDiff line numberDiff line change
@@ -59,16 +59,11 @@ ui-app: {
5959

6060
backend: {
6161
class: component
62-
label: "Backend API\n- Data Fetching\n- Trace Processing"
62+
label: "Static Server"
6363

64-
api_routes: {
64+
static_files: {
6565
class: subcomponent
66-
label: "API Routes\n- /api/messages\n- /api/trace"
67-
}
68-
69-
data_processing: {
70-
class: subcomponent
71-
label: "Data Processing\n- JSON Parsing\n- Data Transformation"
66+
label: "Static React web page\nSimulation traces"
7267
}
7368
}
7469
}
@@ -87,4 +82,3 @@ external-systems: {
8782
ui-app.frontend -> ui-app.backend: "Fetches data from"
8883
ui-app.backend -> external-systems.sim-rs: "Reads trace data from"
8984
ui-app.frontend.components -> ui-app.frontend.state: "Uses for state management"
90-
ui-app.backend.api_routes -> ui-app.backend.data_processing: "Processes data for"

ui/docs/component.png

-47.8 KB
Loading

ui/docs/container.d2

+2-2
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ ui-web-app: {
4949

5050
server: {
5151
class: component
52-
label: "Backend Server\n- API Endpoints\n- Data Processing"
52+
label: "Static backend server"
5353
}
5454
}
5555

@@ -70,5 +70,5 @@ external-systems: {
7070

7171
# Relationships
7272
ui-web-app.browser -> ui-web-app.server: "Requests data from"
73-
ui-web-app.server -> external-systems.trace-file: "Reads data from"
73+
ui-web-app.server -> external-systems.trace-file: "Serves data from"
7474
external-systems.sim-rs -> external-systems.trace-file: "Generates"

ui/docs/container.png

-5.08 KB
Loading

0 commit comments

Comments
 (0)