Skip to content

Commit effd76a

Browse files
committed
visualizer: added view of where TXs are in their lifecycle
1 parent 6f321b0 commit effd76a

File tree

10 files changed

+178
-13
lines changed

10 files changed

+178
-13
lines changed

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

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
use std::collections::{BTreeMap, BTreeSet};
1+
use std::{
2+
collections::{BTreeMap, BTreeSet},
3+
time::Duration,
4+
};
25

36
use serde::Serialize;
47
use sim_core::{
@@ -9,6 +12,24 @@ use sim_core::{
912

1013
use super::{EndorserBlockId, InputBlockId, OutputEvent, VoteBundleId};
1114

15+
#[derive(PartialEq, Eq, PartialOrd, Ord)]
16+
enum TransactionStatus {
17+
Created,
18+
InIb,
19+
InEb,
20+
OnChain,
21+
}
22+
23+
#[derive(Serialize, Default)]
24+
#[serde(rename_all = "camelCase")]
25+
struct TransactionCounts {
26+
timestamp: Timestamp,
27+
created: u64,
28+
in_ib: u64,
29+
in_eb: u64,
30+
on_chain: u64,
31+
}
32+
1233
#[derive(Default)]
1334
pub struct TraceAggregator {
1435
current_time: Timestamp,
@@ -17,15 +38,19 @@ pub struct TraceAggregator {
1738
ibs: BTreeMap<InputBlockId, InputBlock>,
1839
ebs: BTreeMap<EndorserBlockId, EndorsementBlock>,
1940
rbs: Vec<Block>,
41+
tx_counts: Vec<TransactionCounts>,
2042
nodes: BTreeMap<Node, NodeAggregatedData>,
2143
bytes: BTreeMap<MessageId, u64>,
44+
tx_statuses: BTreeMap<TransactionId, TransactionStatus>,
2245
leios_txs: BTreeSet<TransactionId>,
2346
praos_txs: BTreeSet<TransactionId>,
2447
}
2548

2649
impl TraceAggregator {
2750
pub fn new() -> Self {
28-
Self::default()
51+
let mut me = Self::default();
52+
me.tx_counts.push(TransactionCounts::default());
53+
me
2954
}
3055

3156
pub fn process(&mut self, event: OutputEvent) -> Option<AggregatedData> {
@@ -42,6 +67,7 @@ impl TraceAggregator {
4267
bytes: size_bytes,
4368
},
4469
);
70+
self.tx_statuses.insert(id, TransactionStatus::Created);
4571
self.track_data_generated(MessageId::TX(id), publisher, size_bytes);
4672
}
4773
Event::TXSent { id, sender, .. } => {
@@ -73,6 +99,15 @@ impl TraceAggregator {
7399
.collect(),
74100
},
75101
);
102+
for tx in transactions {
103+
let status = self
104+
.tx_statuses
105+
.entry(tx)
106+
.or_insert(TransactionStatus::InIb);
107+
if *status == TransactionStatus::Created {
108+
*status = TransactionStatus::InIb;
109+
}
110+
}
76111
self.track_data_generated(MessageId::IB(id), producer, size_bytes);
77112
}
78113
Event::IBSent { id, sender, .. } => {
@@ -108,6 +143,19 @@ impl TraceAggregator {
108143
.collect(),
109144
},
110145
);
146+
for tx in input_blocks
147+
.iter()
148+
.map(|ib| self.ibs.get(&ib.id).unwrap())
149+
.flat_map(|ib| ib.txs.iter())
150+
{
151+
let status = self
152+
.tx_statuses
153+
.entry(tx.id)
154+
.or_insert(TransactionStatus::InEb);
155+
if matches!(status, TransactionStatus::Created | TransactionStatus::InIb) {
156+
*status = TransactionStatus::InEb;
157+
}
158+
}
111159
self.track_data_generated(MessageId::EB(id), producer, size_bytes);
112160
}
113161
Event::EBSent { id, sender, .. } => {
@@ -140,6 +188,7 @@ impl TraceAggregator {
140188
..
141189
} => {
142190
for id in &transactions {
191+
self.tx_statuses.insert(*id, TransactionStatus::OnChain);
143192
self.praos_txs.insert(*id);
144193
}
145194
for tx in endorsement
@@ -149,6 +198,7 @@ impl TraceAggregator {
149198
.flat_map(|eb| &eb.ibs)
150199
.flat_map(|ib| ib.txs.iter())
151200
{
201+
self.tx_statuses.insert(tx.id, TransactionStatus::OnChain);
152202
self.leios_txs.insert(tx.id);
153203
}
154204
self.rbs.push(Block {
@@ -180,6 +230,10 @@ impl TraceAggregator {
180230
let new_chunk = (event.time_s - Timestamp::zero()).as_millis() / 250;
181231
self.current_time = event.time_s;
182232
if current_chunk != new_chunk {
233+
if new_chunk % 4 == 0 {
234+
let timestamp = Duration::from_secs((new_chunk / 4) as u64).into();
235+
self.tx_counts.push(self.produce_tx_counts(timestamp));
236+
}
183237
Some(self.produce_message())
184238
} else {
185239
None
@@ -194,6 +248,22 @@ impl TraceAggregator {
194248
}
195249
}
196250

251+
fn produce_tx_counts(&self, timestamp: Timestamp) -> TransactionCounts {
252+
let mut tx_counts = TransactionCounts {
253+
timestamp,
254+
..TransactionCounts::default()
255+
};
256+
for status in self.tx_statuses.values() {
257+
match status {
258+
TransactionStatus::Created => tx_counts.created += 1,
259+
TransactionStatus::InIb => tx_counts.in_ib += 1,
260+
TransactionStatus::InEb => tx_counts.in_eb += 1,
261+
TransactionStatus::OnChain => tx_counts.on_chain += 1,
262+
}
263+
}
264+
tx_counts
265+
}
266+
197267
fn produce_message(&mut self) -> AggregatedData {
198268
let nodes_updated = std::mem::take(&mut self.nodes_updated);
199269
AggregatedData {
@@ -204,6 +274,7 @@ impl TraceAggregator {
204274
leios_tx_on_chain: self.leios_txs.len() as u64,
205275
},
206276
blocks: std::mem::take(&mut self.rbs),
277+
transactions: std::mem::take(&mut self.tx_counts),
207278
last_nodes_updated: nodes_updated.into_iter().collect(),
208279
}
209280
}
@@ -243,6 +314,7 @@ pub struct AggregatedData {
243314
nodes: BTreeMap<Node, NodeAggregatedData>,
244315
global: GlobalAggregatedData,
245316
blocks: Vec<Block>,
317+
transactions: Vec<TransactionCounts>,
246318
last_nodes_updated: Vec<Node>,
247319
}
248320

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
version https://git-lfs.github.com/spec/v1
2-
oid sha256:1281503935998e956789bec2e61118220295f3adb2739042dbd83477480ad6a0
3-
size 14666871
2+
oid sha256:4f484999993f16c36feacd5dae30d21f9e8463694150966244ea048386f8026c
3+
size 14706812
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
version https://git-lfs.github.com/spec/v1
2-
oid sha256:5d759f81fccbf40834de524276ac54d2745c5d22ee011bcedc8fc17a70732300
3-
size 12704902
2+
oid sha256:887602dac9aef3193f36c402f7cfca1730dfa36fb00630b86ebb1841b24894cc
3+
size 12774319
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
version https://git-lfs.github.com/spec/v1
2-
oid sha256:3f5a2c6ebf733d1a73e871f011b2925de39a98a38e3e7f0cfba645eb3070b563
3-
size 8610186
2+
oid sha256:79bb79faf0e5149ea2b8dce32e1e14f0d046a460faf1b1d5f6223ed2d0bba077
3+
size 8650616

ui/src/components/Sim/SimWrapper.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { parse } from "yaml";
77
import { Coord2D, Node } from "../../../../data/simulation/topology";
88
import { BlocksView } from "../Blocks/BlocksView";
99
import { GraphWrapper } from "../Graph/GraphWrapper";
10+
import { TransactionsView } from "../Transactions/TransactionsView";
1011
import { Controls } from "./modules/Controls";
1112
import { Scenario } from "./modules/Scenario";
1213
import { Progress } from "./modules/Slider";
@@ -81,12 +82,14 @@ export const SimWrapper: FC = ({
8182
<Stats />
8283
</div>
8384
<div className="flex items-center justify-center gap-4 relative h-screen w-screen">
84-
{activeTab == Tab.Graph && topologyLoaded ? <GraphWrapper /> : null}
85-
{activeTab == Tab.Blocks ? <BlocksView /> : null}
85+
{activeTab === Tab.Graph && topologyLoaded ? <GraphWrapper /> : null}
86+
{activeTab === Tab.Blocks ? <BlocksView /> : null}
87+
{activeTab === Tab.Transactions ? <TransactionsView /> : null}
8688
<div className="absolute top-10 w-full">
8789
<div className="flex justify-center gap-4 min-w-[200px]">
88-
<TabButton name="Graph" active={activeTab == Tab.Graph} onClick={() => setActiveTab(Tab.Graph)} />
89-
<TabButton name="Blocks" active={activeTab == Tab.Blocks && currentBlock === undefined} onClick={() => setActiveTab(Tab.Blocks)} />
90+
<TabButton name="Graph" active={activeTab === Tab.Graph} onClick={() => setActiveTab(Tab.Graph)} />
91+
<TabButton name="Blocks" active={activeTab === Tab.Blocks && currentBlock === undefined} onClick={() => setActiveTab(Tab.Blocks)} />
92+
<TabButton name="Transactions" active={activeTab === Tab.Transactions} onClick={() => setActiveTab(Tab.Transactions)} />
9093
</div>
9194
</div>
9295
<div className="absolute bottom-12 flex w-1/2 gap-4 justify-center">

ui/src/components/Sim/hooks/utils.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ const extractEb = (intermediate: ISimulationIntermediateDataState, ebId: string)
5959
for (const tx of ib.txs) {
6060
if (!intermediate.praosTxs.has(tx)) {
6161
intermediate.leiosTxs.add(tx);
62+
intermediate.txStatuses[tx] = 'onChain';
6263
}
6364
}
6465
const txs = ib.txs.map(tx => intermediate.txs[tx]);
@@ -89,6 +90,7 @@ export const processMessage = (
8990
const { message } = json;
9091

9192
if (message.type === EMessageType.TransactionGenerated) {
93+
intermediate.txStatuses[Number(message.id)] = 'created';
9294
trackDataGenerated(aggregatedData, intermediate, message.publisher, "tx", message.id, message.size_bytes);
9395
intermediate.txs.push({ id: Number(message.id), bytes: message.size_bytes });
9496
} else if (message.type === EMessageType.TransactionSent) {
@@ -98,6 +100,11 @@ export const processMessage = (
98100
} else if (message.type === EMessageType.IBGenerated) {
99101
const bytes = message.transactions.reduce((sum, tx) => sum + (intermediate.bytes.get(`tx-${tx}`) ?? 0), message.header_bytes);
100102
trackDataGenerated(aggregatedData, intermediate, message.producer, "ib", message.id, bytes);
103+
for (const id of message.transactions) {
104+
if (intermediate.txStatuses[id] === 'created') {
105+
intermediate.txStatuses[id] = 'inIb';
106+
}
107+
}
101108
intermediate.ibs.set(message.id, {
102109
slot: message.slot,
103110
pipeline: message.pipeline,
@@ -118,6 +125,7 @@ export const processMessage = (
118125
};
119126
for (const id of message.transactions) {
120127
intermediate.praosTxs.add(id);
128+
intermediate.txStatuses[id] = 'onChain';
121129
}
122130
if (message.endorsement != null) {
123131
bytes += message.endorsement.size_bytes;
@@ -137,6 +145,13 @@ export const processMessage = (
137145
trackDataReceived(aggregatedData, intermediate, message.recipient, "pb", message.id);
138146
} else if (message.type === EMessageType.EBGenerated) {
139147
trackDataGenerated(aggregatedData, intermediate, message.producer, "eb", message.id, message.size_bytes);
148+
for (const { id: ibId } of message.input_blocks) {
149+
for (const tx of intermediate.ibs.get(ibId)?.txs ?? []) {
150+
if (intermediate.txStatuses[tx] === 'created' || intermediate.txStatuses[tx] === 'inIb') {
151+
intermediate.txStatuses[tx] = 'inEb';
152+
}
153+
}
154+
}
140155
intermediate.ebs.set(message.id, {
141156
slot: message.slot,
142157
pipeline: message.pipeline,

ui/src/components/Sim/hooks/worker.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ISimulationAggregatedDataState, ISimulationBlock, ISimulationIntermediateDataState } from '@/contexts/SimContext/types';
1+
import { ISimulationAggregatedDataState, ISimulationBlock, ISimulationIntermediateDataState, ISimulationTransactionData } from '@/contexts/SimContext/types';
22
import * as cbor from 'cborg';
33
import type { ReadableStream } from 'stream/web';
44
import { IServerMessage } from '../types';
@@ -81,10 +81,12 @@ const consumeStream = async (
8181
leiosTxOnChain: 0,
8282
},
8383
blocks: [],
84+
transactions: [],
8485
lastNodesUpdated: [],
8586
};
8687
const intermediate: ISimulationIntermediateDataState = {
8788
txs: [],
89+
txStatuses: [],
8890
leiosTxs: new Set(),
8991
praosTxs: new Set(),
9092
ibs: new Map(),
@@ -94,6 +96,7 @@ const consumeStream = async (
9496

9597
const nodesUpdated = new Set<string>();
9698
let batchEvents = 0;
99+
let lastSec = null;
97100
for await (const { time_s, message } of stream) {
98101
if (message.type.endsWith("Received") && "recipient" in message) {
99102
nodesUpdated.add(message.recipient);
@@ -105,6 +108,19 @@ const consumeStream = async (
105108
nodesUpdated.add(message.id);
106109
}
107110
processMessage({ time_s, message }, aggregatedData, intermediate);
111+
if (lastSec !== Math.floor(time_s)) {
112+
lastSec = Math.floor(time_s);
113+
aggregatedData.transactions.push(intermediate.txStatuses.reduce((acc, curr) => {
114+
acc[curr] += 1;
115+
return acc;
116+
}, {
117+
timestamp: lastSec,
118+
created: 0,
119+
inIb: 0,
120+
inEb: 0,
121+
onChain: 0,
122+
}));
123+
}
108124
aggregatedData.progress = time_s;
109125
batchEvents++;
110126
if (batchEvents === batchSize) {
@@ -141,6 +157,7 @@ const consumeAggregateStream = async (
141157
) => {
142158
let lastTimestamp = 0;
143159
let blocks: ISimulationBlock[] = [];
160+
let transactions: ISimulationTransactionData[] = [];
144161
for await (const aggregatedData of stream) {
145162
const nodes = new Map();
146163
for (const [id, stats] of Object.entries(aggregatedData.nodes)) {
@@ -149,6 +166,8 @@ const consumeAggregateStream = async (
149166
aggregatedData.nodes = nodes;
150167
blocks.push(...aggregatedData.blocks);
151168
aggregatedData.blocks = blocks;
169+
transactions.push(...aggregatedData.transactions);
170+
aggregatedData.transactions = transactions;
152171

153172
const elapsedMs = (aggregatedData.progress - lastTimestamp) * 1000;
154173
lastTimestamp = aggregatedData.progress;
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { useSimContext } from "@/contexts/SimContext/context";
2+
import { FC, useMemo } from "react";
3+
import { Area, AreaChart, Tooltip, XAxis, YAxis } from "recharts";
4+
5+
export const TransactionsView: FC = ({ }) => {
6+
const {
7+
state: {
8+
aggregatedData: {
9+
transactions,
10+
}
11+
},
12+
} = useSimContext();
13+
14+
const data = useMemo(() => {
15+
return transactions.map(data => {
16+
const minutes = Math.floor(data.timestamp / 60);
17+
const seconds = Math.floor(data.timestamp % 60);
18+
return {
19+
Time: minutes ? `${minutes}m${seconds}s` : `${seconds}s`,
20+
"Created": data.created,
21+
"In Input Block": data.inIb,
22+
"In Endorser Block": data.inEb,
23+
"On Chain": data.onChain,
24+
};
25+
})
26+
}, [transactions]);
27+
28+
return (
29+
<div className="flex flex-col w-full h-4/5 items-center justify-center">
30+
<h2 className="font-bold text-xl">Transactions</h2>
31+
<AreaChart width={640} height={480} data={data}>
32+
<XAxis dataKey="Time" />
33+
<YAxis />
34+
<Tooltip />
35+
<Area type="monotone" dataKey="Created" stackId="1" stroke="#26de81" fill="#26de81" />
36+
<Area type="monotone" dataKey="In Input Block" stackId="1" stroke="#2bcbba" fill="#2bcbba" />
37+
<Area type="monotone" dataKey="In Endorser Block" stackId="1" stroke="#4b7bec" fill="#4b7bec" />
38+
<Area type="monotone" dataKey="On Chain" stackId="1" stroke="#2d98da" fill="#2d98da" />
39+
</AreaChart>
40+
</div>
41+
);
42+
}

ui/src/contexts/SimContext/context.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export const defaultAggregatedData: ISimulationAggregatedDataState = {
1010
leiosTxOnChain: 0,
1111
},
1212
blocks: [],
13+
transactions: [],
1314
lastNodesUpdated: []
1415
};
1516

0 commit comments

Comments
 (0)